Custom UI Master Class: Autocomplete TextField (Part 2)

Tim Beals 🎸
Swift2Go
Published in
6 min readNov 9, 2018
Our finished UI component

You can fork the demo project for this article here.

Welcome to the second part of our project! In the previous article, we built a custom subclass of UITextfield, that autocompletes based on a datasource of type String. The textfield has three main behavioural features: It requests it’s own datasource at the moment the user interacts with it; it filters the datasource to provide the best autocompletion match based on the user’s input; and upon tapping the ‘return’ key, it passes the selected String value back to it’s delegate view controller.

So with the textfield complete, let’s try working with autocompletion for a more complex data type. Using an adapter pattern, we will build a new component called AutocompletePerson, which provides an interface for working with Person objects, but filters using Strings under the hood. As you can see above, the component will autocomplete the full name of a person instance in our datasource, and update the imageView before returning the selected person instance upon completion of the name. Let’s get started!

Step 1: Build Datasource Model

We will start by implementing the Person model that we will be working with. Here we have a class with three stored properties firstName, lastName and profileImage which we assign in our custom initializer (lines 11–15). Because we are going to use the full name in our AutocompleteTextField, we can create a computed property that uses string interpolation to return the value we are expecting (lines 7–9). For ease of printing our selected Person instance to the console, we can also adopt CustomStringConvertible and create a suitable description (lines 20–26).

In a real app we would get our datasource by querying the database in runtime but for demo purposes we will do this statically. Here’s a simple approach using an enum:

We start by extending our Person class and then declaring an enum PersonData (lines 2–4). Next we create our 8 cases (lines 8–13). Because enums are a first-class type in their own right, we can declare instance properties which are used as input arguments in our class initializer. To provide values for the first and last name, we declare a computed instance property with a switch inside it. The switch identifies which case has been selected and then returns an appropriate String value (lines 15–39). Our next computed property is an imageName and to keep things simple, I have labelled the images in the assets folder as the first and last names without a space (lines 41–43). Finally we have our Person instance which is initialized with the three previously defined properties (lines 45–47). To get a datasource with all of our people, we simply define a static function with an array literal that creates an instance of each person case and then accesses the person property we created in the previous step (lines 49–58). Now getting our datasource is as simple as: Person.PersonData.allPeople().

Step 2: Layout AutoCompletePerson

With our datasource created, we can turn our attention to today’s UI component AutoCompletePerson. We start by making our component a subclass of UIView, and then define three subviews. The first is profileImage, which is an imageView setup with a default image, contentMode and a layer mask. We require the mask because we want to assign rectangular images to our image view and have them appear as a circle (lines 3–8). Next we instantiate our autoCompleteTextField which we implemented in the previous article. Note that we are making this a lazy var so that we can assign self as our autocompleteDelegate. This has been commented out for now, as we are not quite ready to implement the delegate methods. Finally, we create an underlineView, which is really just a thin line under the autoCompleteTextField for aesthetics (lines 21–25). We add our subviews and set their frames in the method setupSubviews(). Notice that this is also where we create the circle mask for our profileImage using the layer property cornerRadius (lines 41–52).

With the layout complete, we add the AutoCompletePerson to our view controller, and then build and run our app. Hopefully, our UIComponent looks just like the one at the top of our article. Now, let’s give it some functionality!

Step 3: Setup AutocompletePerson Datasources

For simplicity we want to interact with this UI component using only Person type. In other words, we want to hand our component an array of people as a datasource, and we want to return a single instance of a person when a selection is made. As you will remember, AutoCompleteTextField works exclusively with a String datasource, which makes sense. Strings are fairly lightweight and can be easily filtered to find appropriate autocomplete matches. This requires a little datasource setup inside our class to ensure that we are providing a public interface of type Person, but a AutoCompleteTextField datasource of type String.

In the snippet below we create two private properties, a personDatasource and an autoCompleteDatasource (lines 4–10). To set these up, we have a publicly facing method setPersonDatasource(_ input:). In it, we iterate through each person in our input and add them into a dictionary with their full name as the key and then assign the dictionary to our personDatasource (lines 17–24). A dictionary is a great collection type for our purpose, because it allows us to access our selected Person in constant time when the AutoCompleteTextField returns a selected name. Upon assigning our personDatasource, we call setAutoCompleteTextFieldDatasouce() via a property observer (lines 5–7). This datasource needs to be an array of Strings and which we can get very easily by accessing the keys in our personDatasource and casting them to an array (lines 26-35).

Step 4: Implement AutoCompleteTextFieldDelegate Methods

Now that we have our datasource prepared, we can uncomment our AutoCompleteTextFieldDelegate assignment and implement its three methods. When the user initiates interaction tapping the textfield provideDatasource() is called, and we assign our String array stored in autoCompleteDatasource (lines 11–13). The returned(with selection:) method allows us to access the selected person instance from our personDatasource using the input argument as a key. With the selection, we can update the profileImage, and set our new selectedPerson property (lines 15–22). Finally, when the user clears the textfield, we can reset our profileImage to the default and our selectedPerson to nil using textfieldCleared() (lines 24–27).

Step 5: Create AutoCompletePersonDelegate

By now, our textfield should be autocompleting and selecting a person from our datasource. So all that is left to do is create a delegate to pass our selected person back to our view controller. We create a protocol AutoCompletePersonDelegate and declare one method which accepts the selected person as the input argument (lines 1–3). We create a delegate property of our protocol type (line 13) and update our selectedPerson property so that our delegate method is called inside a property observer (lines 7–11). Now when we set our selectedPerson our delegate passes the it back to the delegate (view controller).

Over in our view controller, we can add some new lines of code to have our UI component functioning fully. We create a selectedPerson property which prints to the console through a property observer (lines 5–9). Then we conform to AutoCompletePersonDelegate and implement our selectedPerson(_ person:) method, which updates the selectedPerson property (lines 47–51). Finally, in the method layoutAutocompletePerson() we can set our view controller as the delegate and set the Person datasource in our AutoCompletePerson (lines 40–41). Now we can build and run our app and see the full effect of our custom UI component.

Wrap Up

And that’s it! In this article we took a functioning AutocompleteTextField and built a kind of adapter around it that provides a public interface for a more complex datatype in the form of our Person model. We manipulated our data input so that the textfield could still work by filtering Strings, and we used a dictionary to access our selected Person in constant time using the full name as a key. Using this kind of approach you can modify your textfield to work with any kind of data model that you want.

As usual, thanks for reading!

If you haven’t yet read the first article for this project you can find it here. Hungry for more custom UI? Build an infinite paging scroll view!

--

--