Implementing Auto-Complete with UITextField

11/13/2018 Updated for Xcode 10/Swift 4.2

I recently wanted to add a Safari-like auto-complete feature to an iOS app I was working on. Specifically, I wanted the app to proactively suggest a complete word based on some initial characters entered by the user, similar to how Safari suggests URLs based on the first few letters in a web address:

As in Safari, tapping Return would allow the user to confirm the suggestion.

Since this is not a feature that Apple provides "out of the box", I thought I would share the approach I took in case it is of use to anyone.

In this example, the text field will suggest values for the user's favorite color:

As the user types, a list of options is consulted to determine which value to suggest:

Suggestions are defined as an array of strings:

let suggestions = [
    "red",
    "orange",
    "yellow",
    "green",
    "blue",
    "purple"
]

To handle user input, the view controller assigns itself as the text field's delegate and implements the textField(_:shouldChangeCharactersIn:replacementString:) method, as shown below:

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    return !autoCompleteText(in: textField, using: string, suggestions: suggestions)
}

This method simply delegates to the following method, which searches the suggestion list for the first entry with a prefix that matches the user's input. It then updates the text value with the matching suggestion and selects the remaining characters in the text field:

func autoCompleteText(in textField: UITextField, using string: String, suggestions: [String]) -> Bool {
    if !string.isEmpty,
        let selectedTextRange = textField.selectedTextRange, selectedTextRange.end == textField.endOfDocument,
        let prefixRange = textField.textRange(from: textField.beginningOfDocument, to: selectedTextRange.start),
        let text = textField.text(in: prefixRange) {
        let prefix = text + string
        let matches = suggestions.filter { $0.hasPrefix(prefix) }

        if (matches.count > 0) {
            textField.text = matches[0]

            if let start = textField.position(from: textField.beginningOfDocument, offset: prefix.count) {
                textField.selectedTextRange = textField.textRange(from: start, to: textField.endOfDocument)

                return true
            }
        }
    }

    return false
}

The method returns true if a match was found and false otherwise. The delegate method returns the inverse of this value so the text field will continue to process keystrokes when a match is not found.

Finally, the controller implements the delegate's textFieldShouldReturn(_:) method to "confirm" the suggestion:

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    textField.resignFirstResponder()

    return true
}

Note that the text field's autocapitalizationType and autocorrectionType properties were set to .none and .no, respectively. Disabling auto-capitalization ensures that the lookup logic will correctly identify matches, since all of the suggestions begin with lowercase letters. Turning off auto-correction ensures that iOS's built-in suggestion bar is not displayed, since suggestions will be made by the text field itself.

Complete source code for this example can be found here.