read

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:

  1. Presenting VC - where the search is triggered
  2. 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.

There are 2 ways.

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.


Image

@samwize

¯\_(ツ)_/¯

Back to Home