Reputation: 1289
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:
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)
}
Upvotes: 11
Views: 10421
Reputation: 3831
@pbush25
the correct name for the method you suggested which is located in UISearchBarDelegate as per documentation is
// @available(iOS 2.0, *)
// optional func searchBarSearchButtonClicked(_ searchBar: UISearchBar) // called when keyboard search button pressed
Upvotes: 0
Reputation: 869
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.
Upvotes: 11
Reputation: 5258
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.
Upvotes: 10