Though UISearchDisplayController is seemingly easy (and yes it’s easy), apart from the sample source code, there isn’t much documentation available from Apple. I won’t be posting code for this tutorial, (as most of them come from Apple’s own source code), however, the tutorial will contain code fragments that I wish to highlight and those I changed for improving the search using NSPredicate

Step 1: Create the project

If you haven’t created a project yet, create a Navigation Based Project and open the RootViewController.xib.

Step 2: Modify the XIB

Drag UISearchDisplayController to your RootViewController.xib. (It automatically does the connections for you) Drag the SearchBar that gets added onto your TableView. You need the UISearchBar to be displayed on the controller. So drag the searchbar and place it on the top of the table view. Add ScopeBars if you need any. We will discuss later how to handle the scope bar changes and how to filter text based on the selected scope.

Step 3: The Design of UISearchDisplayController

In a traditional tableviewcontroller, you normally have one master list of data which you use for all the tableview data source methods. In case of UISearchDisplayController, you have to maintain two lists. One master list and one a filtered list. The tableview data source delegates will be called by the framework when it needs to populate your tableview or the search results. So in all the methods you have to differentiate whether the data source call is for populating the filtered list or for populating the main list.

This is done by a simple two line code.

if (tableView == self.tableView) {
// normal table view population
if(tableView == self.searchDisplayController.searchResultsTableView){
// search view population

This code should be written in cellForRowAtIndexPath, didSelectRowAtIndexPath and numberOfRowsInSection.
For the complete code listing, see Apple’s sample code.

Step 3: Implement the search delegates

When the user invokes a search,the UISearchDisplayDelegate functions you implement will be called. The two main functions that you have to implement are,

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller 
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller 
shouldReloadTableForSearchString:(NSString *)searchString

Copy the implementation from Apple’s source.In the header file, your class should implement UISearchBarDelegate and UISearchDisplayDelegate.

As the name suggests, the first function will be called when the user changes the scope button and the second function is called when he starts typing a search query. The first function is not necessary if you haven’t added any scope button in step 2.

Step 4: Filtering your data using NSPredicate

This step is the reason why I wrote this seemingly simple post. I didn’t like the Apple’s way of filtering. It doesn’t match “contains” text. I modified the filterContentForSearchText method, so as to use NSPredicate. You still need the Apple’s implementation of shouldReloadTableForSearchString and shouldReloadTableForSearchScope.
The filterContentForSearchText implementation CANNOT be copy-pasted into your application. It’s a code fragment that doesn’t compile without some changes. Points to note in the function is the use of predicate.

NSPredicate *predicate = 
[NSPredicate predicateWithFormat:@"(SELF contains[cd] %@)", searchText];

The [cd] in the predicate format means case and diatric insensitive. the verb I have used is “contains”, you can change it to beginsWith, endsWith, like and many others. The NSPredicate is a very powerful feature that can be exploited here. I don’t understand why Apple just didn’t use it :-( For more info, look at Apple’s NSPredicate documentation here

Below is the complete code listing after making the NSPredicate changes.

- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
    [self.filteredData removeAllObjects]; // First clear the filtered array.
    for (Product *product in tableData)
        NSPredicate *predicate = [NSPredicate predicateWithFormat:
           @"(SELF contains[cd] %@)", searchText];
       [product.productID compare:searchText options:NSCaseInsensitiveSearch];
        BOOL resultID = [predicate evaluateWithObject:product.productID];
        BOOL resultName = [predicate evaluateWithObject:product.productName];
       if([scope isEqualToString:@"Product ID"] && resultID)
            [self.filteredData addObject:product];
        if([scope isEqualToString:@"Product Name"] && resultName)
            [self.filteredData addObject:product];
        if([scope isEqualToString:@"Any"] && (resultID || resultName))
            [self.filteredData addObject:product];

With NSPredicate, you can play around with the search filters. NSPredicate is as powerful and easier to use than Regular Expressions. Try your hand at it.


Follow me on Twitter