Stuart Breckenridge

Implementing UISearchController

With iOS 8, Apple introduced a more streamlined approach to incorporating a UISearchBar and associated search functionality. In this post, we look at how to implement UISearchController.

Before we look at UISearchController, we need to create a UITableView and seed it with some data. I’ll spare you the boilerplate of creating a table view, but with WWDC around the corner—where we’re going to learn about the future of OS X (or is it macOS?)—my data source is a run down of the history of OS X releases. Topical, right? The data source—history—is created in a separate class called OSX, along with a searchResults array:

class OSX {
    static let sharedOSX = OSX()
    
    var history:[Dictionary<String,Any>] = [
        [
            "name" : "Cheetah",
            "version" : "10.0",
            "released" : "March 24, 2001",
            "image" : "Cheetah"
        ],
        [
            "name" : "Puma",
            "version" : "10.1",
            "released" : "September 25, 2001",
            "image" : "Cheetah"
        ],
        [
            "name" : "Jaguar",
            "version" : "10.2",
            "released" : "August 23, 2002",
            "image" : "Jaguar"
        ]
        
        // and so on...
        
        
        ]
        
    var searchResults = [Dictionary<String,Any>]()
        
}

When populated into the tableview it looks like this:

What do we do to add a UISearchController to the tableview? It’s really quite simple. Within your tableview’s view controller, create a lazy variable for your UISearchController and configure it as you see fit. My example implementation is below:

lazy var searchController:UISearchController = ({
        let controller = UISearchController(searchResultsController: nil) // 1
        controller.hidesNavigationBarDuringPresentation = false // 2
        controller.dimsBackgroundDuringPresentation = false // 3
        controller.searchBar.searchBarStyle = .Minimal // 4
        controller.searchResultsUpdater = self // 5
        return controller
    })()

In the order of what is going on here:

  1. If you want to present search results in the current view controller, pass nil as the parameter.
  2. I want to keep the UINavigationBar visible when using the search bar, so this property is set to false.
  3. If you want the background to be dimmed during a search, use true, for this implementation, I’m keeping it as false.
  4. It’s personal preference which search bar style you use. I’m using .Minimal.
  5. In order for a UISearchController to work, you have to assign an object that conforms to the UISearchResultsUpdating protocol. For this sample code, I’m assigning it to self, which is the view controller.

To make the searchController’s searchBar visible, you add a one-liner in viewDidLoad() setting the searchBar as the tableHeaderView:

osXTableView.tableHeaderView = searchController.searchBar

We then need to ensure that our view controller conforms to the UISearchResultsUpdating protocol:

extension ViewController:UISearchResultsUpdating
{
    func updateSearchResultsForSearchController(searchController: UISearchController) {
        OSX.sharedOSX.searchResults = OSX.sharedOSX.history.filter({
            ($0["name"] as! String).lowercaseString.containsString(searchController.searchBar.text!.lowercaseString) ||
            ($0["version"] as! String).lowercaseString.containsString(searchController.searchBar.text!.lowercaseString) ||
            ($0["released"] as! String).lowercaseString.containsString(searchController.searchBar.text!.lowercaseString)
        })
        osXTableView.reloadData()
    }
}

In this method we are updating the searchResults array by filtering the history array based on the text in the searchController’s searchBar1. Once the array has been filtered, the tableView is reloaded.

Finally, in order to ensure that the searchResults are displayed correctly, we have to make two small amendments to numberOfRowsInSection and cellForRowAtIndexPath to accomodate for the searchController being active:

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        switch searchController.active {
        case true:
            return OSX.sharedOSX.searchResults.count
        case false:
            return OSX.sharedOSX.history.count
        }
    }
    
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
	let cell = tableView.dequeueReusableCellWithIdentifier("OSXCell") as! OSXCell
	
	switch searchController.active {
	case true:
		cell.configureSearchCell(indexPath)
	case false:
		cell.configureCell(indexPath)
	}
	
	return cell
}

The only difference between configureSearchCell and configureCell is the array from which each method retrieves data.

Once all the code is in place, we now have a working search bar!

The source code for this example is available on Github.

Recommended Reading:

  1. UISearchController Class Reference (via Apple)
  2. UISearchResultsUpdating Protocol Reference (via Apple)
  1. The strings contained within the data source and the search bar are lowercased so that, for example, “Ee” in the search bar will correctly match the “ee” from Cheetah. ↩︎


Supported by