UISearchController only update on search button click

Charlie picture Charlie · Oct 13, 2015 · Viewed 8.7k times · Source

I have a UISearchController which is configured to search a very large array of data. As such, when I am typing in the search bar, it takes a very long time to actually type out my search. It performs the search comparison with every character entry in the search field, which is very slow.

I am wondering how to fix this. My thoughts are:

  1. only perform the search after the user finishes typing and hits the search button
  2. perform the search on a background thread, freeing up the main thread for keyboard entry

I would like to use method 1 but I can't seem to figure out how to do this with the new UISearchController.

Below is my relevant project code:

class AirportSearchTBVC: UITableViewController, UISearchResultsUpdating{
var airportData = [Dictionary<String, String>]()
var filteredData = [Dictionary<String, String>]()
var searchController = UISearchController()
override func viewDidLoad() {
    super.viewDidLoad()


    searchController = UISearchController(searchResultsController: nil)
    searchController.searchResultsUpdater = self

    searchController.dimsBackgroundDuringPresentation = false

    searchController.searchBar.sizeToFit()
    tableView.tableHeaderView = searchController.searchBar

    definesPresentationContext = true


    searchController.hidesNavigationBarDuringPresentation = false

    self.tableView.reloadData()
}

func updateSearchResultsForSearchController(searchController: UISearchController) {

    filteredData = []

    let searchPredicate = NSPredicate(format: "SELF CONTAINS[cd] %@", searchController.searchBar.text!)
    let array = (airportData as NSArray).filteredArrayUsingPredicate(searchPredicate)

    filteredData = array as! [Dictionary<String, String>]

    self.tableView.reloadData()

}

Bonus question If I search for a string, it doesn't appear to be returning any results if the string doesn't match perfectly. For example: "orida" does not find "Florida". Isn't my search predicate supposed to find this using CONTAINS?

Update This code nearly works, but it basically throws a bunch of stuff on the background thread and then chugs through it. The keyboard is lively now, but it seems to crash if I change things in the text field quickly on it while dismissing and re-entering the search bar...

func updateSearchResultsForSearchController(searchController: UISearchController) {
    appDel.backgroundThread(background: {
        self.filteredData.removeAll(keepCapacity: false)

        let searchPredicate = NSPredicate(format: "SELF CONTAINS[cd] %@", searchController.searchBar.text!)
        let array = (self.airportData as NSArray).filteredArrayUsingPredicate(searchPredicate)

        self.filteredData = array as! [Dictionary<String, String>]
        },
        completion: {

            dispatch_async(dispatch_get_main_queue()) {
                    self.tableView.reloadData()
            }

    });






}

Update 2 After playing a bit with it, I was able to get it to work pretty decently by both waiting for a searchBar.text length >= 3 characters as well as making sure the character count didn't change within 1 second of the updateSearchResultsForSearchController: being called. The combination of these as well as integrating a search button execute command should resolve my problem.

func updateSearchResultsForSearchController(searchController: UISearchController) {
    let startCount = searchController.searchBar.text!.length
    delay(1) {
    if searchController.searchBar.text!.length >= 3 && searchController.searchBar.text!.length == startCount{
        self.view.addSubview(self.progressHud)
        self.appDel.backgroundThread(background: {
            self.filteredData.removeAll(keepCapacity: false)

            let searchPredicate = NSPredicate(format: "SELF CONTAINS[cd] %@", searchController.searchBar.text!)
            let array = (self.airportData as NSArray).filteredArrayUsingPredicate(searchPredicate)

            self.filteredData = array as! [Dictionary<String, String>]
            },
            completion: {
                dispatch_async(dispatch_get_main_queue()) {
                    self.tableView.reloadData()
                    self.progressHud.removeFromSuperview()
                }



        });
    }
    }





}
    func delay(delay:Double, closure:()->()) {
        dispatch_after(
            dispatch_time(
                DISPATCH_TIME_NOW,
                Int64(delay * Double(NSEC_PER_SEC))
            ),
            dispatch_get_main_queue(), closure)
    }

Answer

pbush25 picture pbush25 · Oct 13, 2015

You can use the delegate method searchBarSearchButtonPressed: to monitor when the search button is pressed. Set a flag when this delegate method is executed and then in your searchBarTextDidChange: method you can check this flag to see if you should execute the search. Of course this will only search after the user presses search, and most people expect to see something happening while they're typing.