Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UISearchController only update on search button click

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)
    }
like image 798
Charlie Avatar asked Oct 13 '15 15:10

Charlie


2 Answers

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.

like image 60
pbush25 Avatar answered Oct 27 '22 00:10

pbush25


If you would like to only perform the search when the user clicks the "Search" button - which seems both sensible and reasonable for a larger and slower access data set, you can do it like this:

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.searchBar.delegate = self

    searchController.dimsBackgroundDuringPresentation = false

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

    definesPresentationContext = true


    searchController.hidesNavigationBarDuringPresentation = false

    self.tableView.reloadData()
}

func searchBarSearchButtonClicked(searchBar: UISearchBar) {
    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()

}
func updateSearchResultsForSearchController(searchController: UISearchController) {

}

The key differences are to set the searchController.searchBar.delegate = self, and to put your search code in the searchBarSearchButtonClicked function instead of the updateSearchResultsForSearchController - which should now do nothing. I apologise for any errors, I tested this code using Xcode v7.0.1 Objective-C, and then transposed the changes.

like image 28
kshepherd Avatar answered Oct 27 '22 01:10

kshepherd