Custom UI Master Class: Autocomplete TextField (Part 1)

Tim Beals 🎸
Swift2Go
Published in
7 min readNov 4, 2018
Our finished UI component

The success of any app rides on user retention, which in turn relies heavily on successful user experience and interface design. When designing an app, we need to ensure that our user is able to achieve what they want through minimal and intuitive interaction. Moreover, the interaction needs to be engaging, and even fun. This is the second article in an ongoing series about developing custom UI components that achieve all of these ideals.

You can fork the demo project for this article here.

Read part two for this project here.

Autocomplete TextField: Use Case

If there is one UI interaction that I dislike most, it has to be typing information into textfields. Compared to the broad and swift gestures that other UI components allow, typing requires a lot from the user in terms of concentration and precise movement. As a general rule, app designers should avoid textfields in favour of simpler alternatives wherever possible.

In some rare cases textfield entries will be entirely unique, but in most cases they won’t. Especially if your app is storing user data across a large network, the chances are that someone, somewhere has entered the information that your current user is about to laboriously type, one character after another until it is complete. In these situations, autocomplete textfields make sense. The number of possible inputs is large enough that it will take a long time for the user to find and make a selection themselves, but is small enough to be quickly processed by a searching algorithm to find a shortlist of possible matches and return the best candidate.

Across two articles, we will implement an autocomplete textfield (as seen above) that will receive user input for a person’s name, search for matches and return the selected Person object. In this first article, we will create the custom textfield and work with a datasource of Strings. Let’s get started!

Step 1: Initial Setup

We are going to be adding autocomplete logic to the existing UITextField component, so we begin by creating our custom class AutoCompleteTextField, subclassing UITextField (line 1). Next, we create a datasource property of type [String]? which will be the collection of values that the user might put into the textfield, and which we will filter to get possible completions (line 3). In the above gif of our completed UI element, you will notice that we have different colours for the entered text and suggested completion, so the next two properties are colours with default values. The lightTextColor also has a property observer so that the underlying textColor property is set when the value changes (lines 5–11).

The logic for filtering our database needs to be called as the user interacts with the textfield, so we adopt the UITextFieldDelegate (line 26), and set self as our own delegate (line 17).

Step 2: Get Datasource

It’s probable that the datasource for our AutoCompleteTextField will not be static, but will change during the application runtime. In this demo, you could imagine that new Person objects are created while the application is running, and if we want them to be included in our datasource our textfield needs to request a datasource from our view controller at the moment the user begins interaction with it. The best way to do this is with a delegate pattern.

We create a protocol AutoCompleteTextFieldDelegate with a method declaration provideDatasource() (lines 1–3). Then we create a delegate property. In this case the ‘delegate property name refers to the UITextField delegate which we used in the previous snippet. Therefore we use a unique name autocompleteDelegate (line 7). We want our delegate to provide the datasource the moment the user taps the textfield, which can be achieved using the UITextFieldDelegate method textFieldShouldBeginEditing(textField:). Because we want the textfield text to show not only what the user has entered, but also the proposed completion, we need to store the user input in a separate property currInput. We also want to indicate whether the user has finished providing their input with a bool isReturned. Our delegate method requests a datasource and resets the currInput and isReturned properties appropriately (lines16–21).

Over in the view controller, we can now initialize our autoCompleteTextField property with the colour options and placeholder text we desire (lines 3–11). Next, we provide a frame, add our component as a subview and set our self as autocompleteDelegate in the override method viewWillLayoutSubviews (lines 29–39). Finally we adopt the AutoCompleteTextFieldDelegate protocol and in the method provideDatasource, we create some dummy data and assign it to the datasource property in our UI component (lines 43–50).

When we build and run, we can add a breakpoint in the provideDatasource method and check that the datasource is being passed correctly when we tap on the textfield.

Step 3: Implement Filtering Logic

We want our AutoCompleteTextField to update the suggested completion with each added character, so the best place to do this is in the delegate method textField(_ textField:, shouldChangeCharactersIn: replacementString:). This method is called with every new keystroke and provides the textfield, the newly entered character as a string, and the range of the new character in the textField text property. There are four methods to create, so let’s go through them one at a time.

updateText(_ string:, in textField:) (lines 22–25): We set our textField text colour to the lightTextColor property as we will be working with NSMutableAttributedStrings with multiple text colours in later methods. Next, we append the newly entered character (as String) to our currInput property and update our textfield’s text property with the user’s updated entry.

testBackspace(_ string:, in textField:) (lines 27–38): Touching the backspace creates a special character so to test for it, we convert the string into a CChar and then compare it to it’s unicode representation “\u{8}” using the strcmp (string compare) method. If this test succeeds, we drop the last character from our currInput property and test to see if our string is now empty. If it is, we clear the textfield text entirely and let our autocompleteDelegate know that our textfield has cleared using the delegate method textFieldCleared() which has been declared on line 3.

findDatasourceMatch(for textField: ) (lines 40–51): First, we check that we have a datasource to work with using a guard statement. Then we get a shortlist of possible autocomplete options by filtering the datasource with the current input as a prefix. For this demo, we don’t care which possibility is suggested when there are several options, but we do want to return an exact match if it exists, so we filter our shortlisted options for an exact match. If one exists we assign it to fullName , otherwise we get the first result from all possible matches, and if this is also empty we use our current input. Visually we want to represent our match with the range of our current input in our bold text colour while the remainder of the string is our light colour. We achieve this using NSMutableAttributedString. First we get the range of our current input within our fullName constant and convert it from type Range to NSRange using an extension method (lines 62–64). Having done this, we can add our customized bold colour as the foreground colour attribute to the specified range of our attributed string. Finally we set the attributed string to the attributedText property of our textfield.

updateCursorPosition(in textField: ) (lines 54–58): Finally we need to update our cursor position. To do this we create a UITextPosition object using the position(from position: , offset: ) method in our textfield and assign the selectedTextRange using this position.

Now if we build and run, we should be able to type into our textfield and see the autocomplete options updating. The text in the textfield should have bold and light colouring to indicate the typed text versus the suggested autocompletion and the cursor should update its position, separating the two colours.

Step 4: Pass and Clear Selection

While our UI component certainly looks how we want, there is a little more implementation required to confirm a selection based on a return key tap. Again we turn to our UITextFieldDelegate methods.

textFieldShouldReturn(_ textField: ) (lines 9–14): This method gets called when the return key is tapped. We create a new method in our AutoCompleteTextFieldDelegate protocol returned(with selection:) (line 3), and call it passing in the textfield text which has previously been set as the suggested autocomplete option. We set our isReturned boolean and end editing in our textfield, which triggers our next method.

textFieldDidEndEditing(_ textField: ) (lines 16–24): With editing ended we want to resign the first responder, causing the software keyboard to animate away, and then check if we have returned a selection. If we have, we want all of the text in the textfield to be our bold colour otherwise we want our textfield text and currInput to be reset to empty strings.

To test that our autocompleteDelegate is working correctly, we can create a selection property in our view controller with a print statement being called each time it is set (lines 3–7). In our two new delegate methods we can set the selection property with a string or set it to nil when the textfield is cleared. Again, we build and run our application checking that our selection print statements correctly reflect the selection and clearing of our custom AutoCompleteTextField.

Wrap Up

And that’s it for the first part of this custom UI master class. We have created a component that works very well for autocompleting a datasource of Strings. In order to achieve this, we worked with lots of UITextFieldDelegate methods and did some fairly complex string manipulation using ranges and prefixing. We also created a very simple interface for our view controller using the delegate pattern. With very little setup in our view controller, we were able to add the UI component as a subview, deliver it a datasource at the appropriate time, and update our selection in a single line of code.

In our next article, we will build an AutoCompletePerson class which will demonstrate how to use our AutoCompleteTextField to make selections for a datasource of a more complex type. Thank you for reading and for the overwhelmingly positive response to my article last week on the Infinite Paging Scroll View.

--

--