Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create autocomplete text field in Swift

What I want to be able to create is an auto complete text field in iOS.

I have a form for selecting a client, wherein the user must select a client once using a text field . What I want to happen is when the user writes the first three letters on the text field, I want some service to run a remote web service query using the entered text and present the query results as auto complete suggestions.

Below is my current code for my app (iPad only).

   import UIKit

    class AddClientViewController: UIViewController, UITextFieldDelegate {

        @IBOutlet weak var clientTextField:  UITextField!

        var foundList = [String]()


    override func viewDidLoad() {
        super.viewDidLoad()



         let listUrlString =  "http://bla.com/myTextField.php?field=\(clientTextField)"
        let myUrl = NSURL(string: listUrlString);
        let request = NSMutableURLRequest(URL:myUrl!);
        request.HTTPMethod = "GET";

        let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
            data, response, error in

            if error != nil {
                print(error!.localizedDescription)
                dispatch_sync(dispatch_get_main_queue(),{
                    AWLoader.hide()

                })

                return
            }


            do {

                let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSArray

                if let parseJSON = json {


                    self.foundList = parseJSON as! [String]

                }


        } catch {

            print(error)

        }
    }

    task.resume()
}

Here is the json output that my web service provides.

["123,John", "343,Smith", "345,April"]

Separated by commas, the first parameter is the client ID and the second parameter is the name of the client. John is the name so it should be presented in the auto complete suggestions, which if selected will set the text of the clientTextField to John.

The current text content of the clientTextField is passed as a GET parameter to my webservice.

I don't know how to do this. The user could be typing and not yet finished, while multiple queries could already have been sent.

like image 276
SwiftDeveloper Avatar asked Feb 25 '16 13:02

SwiftDeveloper


2 Answers

I did something like this in my app for looking up contacts. I will pseudo code this out for you to understand the concept:

1) Capture the characters entered into the textfield by the enduser

2) At some character count entered decide to query the server to return all entries that match - choose the character count you are comfortable with (I chose around 3-4 characters). Fewer returns more, more returns less obviously...up to you, perf and UX considerations.

3) Put the results of this server query into an array on the client. This will be your superset from which you will offer the suggestions to the user.

4) After each subsequent character entered into the text field you will now filter the array (array.filter()) by character string entered to this point. 5) tableView.reloadData() against the filtered array at each character entered.

6) I use a dataFlag variable to determine what datasource to show in the tableview depending on what the user is doing.

Note: You only query the server once to minimize perf impact

// this function is called automatically when the search control get user focus
func updateSearchResults(for searchController: UISearchController) {
  let searchBar = searchController.searchBar
  if searchBar.text?.range(of: "@") != nil {
    self.getUserByEmail(searchBar.text!)
  }
  if searchController.searchBar.text?.characters.count == 0 && dataFlag != "showParticipants" {
    dataFlag = "showInitSearchData"
    self.contacts.removeAll()
    self.participantTableView.reloadData()
  }
  if dataFlag == "showInitSearchData" && searchController.searchBar.text?.characters.count == 2 {
    self.loadInitialDataSet() {
      self.dataFlag = "showFilteredSearchData"
    }
  }
  if dataFlag == "showFilteredSearchData" {
    self.filterDataForSearchString()
  }

}

// filter results by textfield string
func filterDataForSearchString() {
  let searchString = searchController.searchBar.text
  self.filteredContacts =  self.contacts.filter({
    (contact) -> Bool in
    let contactText: NSString = "\(contact.givenName) \(contact.familyName)" as NSString

  return (contactText.range(of: searchString!, options: NSString.CompareOptions.caseInsensitive).location) != NSNotFound
  })

  DispatchQueue.main.async {
    self.participantTableView.reloadData()
  }  
}
like image 82
mafarja Avatar answered Oct 14 '22 13:10

mafarja


Using Trie like structure will be a better option here. Based on entered string, trie will return top keywords (lets say 10) starting with the entered string. Implementing this trie on server side is better. When UI makes http call, calculations will be done on server side and server will send top results to UI. Then, UI will update TableView with new data.

You can also do this with hashmap/dictionary but performance will be worse. Using trie/prefix tree approach will give you the best performance when you have thousands or millions of strings to check.

like image 1
yalcin Avatar answered Oct 14 '22 13:10

yalcin