UISearchController is a new controller in iOS 8 to handle search.
Prior to iOS 8, we have UISearchDisplayController, which is deprecated. UISearchController
more than replaced it, with a architectural change.
The Architecture
There are two View Controllers (VC) involved in search. Let’s call them:
- Presenting VC - where the search is triggered
- Results VC - where the results are displayed in
You may also have the presenting VC display the results. In that case, they are the same VC.
UISearchController
also provides a UISearchBar
object, because all search requires the search bar for input.
Triggering the search
There are 2 ways.
1. Add Search Bar
Add the UISearchBar
to your presenting VC.
This must be done programatically, because there is no library object for UISearchController
in Xcode. And you have to use it’s searchBar
object.
If you are using a UITableView
, a search bar can be added to the header easily:
tableView.tableHeaderView = searchController.searchBar
2. Add Search Button
Another way is to have a button to trigger the search, instead of adding the whole search bar in like in (1).
Create the IBAction
for the button:
@IBAction func tapSearch(sender: AnyObject) {
presentViewController(searchController, animated: true, completion: nil)
}
Remember: searchController
is a UIViewController
(read in later section) so it can be called with presentViewController
.
When the presenting VC has a navigation bar, you will need to configure search controller:
searchController.hidesNavigationBarDuringPresentation = false
The Delegates
Delegate #1 - UISearchResultsUpdating
UISearchResultsUpdating
protocol has a callback when the user enters into the search bar.
Set searchResultsUpdater
. Typically, the results VC will implement the protocol, so that it will update the results accordingly.
searchController.searchResultsUpdater = resultsViewController
Then in resultsViewController
, implement the method:
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchTerm = searchController.searchBar.text
// Update your results
}
Delegate #2 - UISearchBarDelegate
UISearchBarDelegate
protocol provides more events:
- text changed
- should text change
- should/begin/end editing
- tap on cancel button/etc
Set searchBar.delegate
. Typically, the results VC.
searchController.searchBar.delegate = resultsViewController
You might be thinking we have searchResultsUpdater
. Isn’t that enough? Usually so, unless you want to know when buttons such as scopes button are tapped on.
Delegate #3 - UISearchControllerDelegate
UISearchControllerDelegate
protocol provides events when:
- the search controller is presented or dismissed
Set delegate
to the view controller that handles the calls, typically the presenting VC.
searchController.delegate = self
UISearchController is a UIViewController
UISearchController
inherits from UIViewController
.
You can present it modally with presentViewController
.
BUT, you should never push to navigation controller or use it as a child etc. If you want that, you can use UISearchContainerViewController to wrap it first.
Display Results Instead of Dimming
The default behaviour dims the presenting VC when search is triggered.
User has to type 1 character, then the results VC will be shown.
It is common UX to display an intial set of results once search is triggered. Who knows, our smart filtering might already show up a good match?
Firstly, we prevent the dim with:
searchController.dimsBackgroundDuringPresentation = false
To show the results VC, a little hack is needed in the results VC:
class ResultsViewController: UIViewController {
var context = 0
override func viewDidLoad() {
super.viewDidLoad()
setupToPreventHiddenBehaviour()
}
func setupToPreventHiddenBehaviour() {
view.addObserver(self, forKeyPath: "hidden", options: [ .New, .Old ], context: &context)
}
deinit {
view.removeObserver(self, forKeyPath: "hidden")
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
guard context == &self.context else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
return
}
if change?[NSKeyValueChangeNewKey] as? Bool == true {
view.hidden = false
}
}
}
It hacks around by observing for the view’s hidden
property, forcing it to never hide. Even when you clear the search bar, it gets back to this initial state.