Updating to the iOS 8 Search Controller

When I first wrote about adding a search bar to a table view we were still using iOS 5. Not much changed with search bars until Apple deprecated UISearchDisplayController in iOS 8 and replaced it with UISearchController. This post will revisit that original project to update it for the new UISearchController.

App Modernization

As a sidenote I thought I would also take the opportunity to modernise the code and make the app iOS 8 only. The list of changes is a reminder of how much has changed since iOS 5:

  • Use modules and remove precompiled header
  • Convert to a Universal app and Storyboard
  • Use Auto Layout
  • Use Dynamic Type
  • Use Asset Catalogs for icons and images
  • Use Base Internationalization
  • Remove old orientation handling

The updated user interface uses a split view controller on both an iPad and iPhone - something that was not possible in the old days of iOS 5. The master view shows a list of countries that can be filtered by the search bar. The detail view shows the selected country (screenshot is cropped to top left corner):

The search bar when active hides the navigation view and shows the search results (the screenshot this time is for the iPhone interface):

A Recap of UISearchDisplayController

Before looking at the new approach to managing a search bar here is quick recap of the now deprecated UISearchDisplayController class and delegates:

UISearchDisplayController

You initialise a search display controller with a search bar and a view controller to manage the content. Once the user activates the search the search display controller overlays the search results view over the original content. When the content view controller was a table view controller it was typical to also make it the search display controller delegate, data source and delegate for the search results table view and delegate for the search bar. You can see this approach in my original post.

The WWDC 2014 session 228 - A Look Inside Presentation Controllers describes the limitations and problems with UISearchDisplayController. I could summarise by saying that the search bar and search results views tended to have a mind of their own…

The New and Shiny UISearchController

The UISearchController class replaces UISearchDisplayController and has a simpler set of protocols:

UISearchController

Creating the Search Display Controller

In the past if you wanted to construct a search bar interface with Interface Builder you would drag the Search Bar and Search Display Controller objects into the Storyboard. This created UISearchBar and UISearchDisplayController objects with the various delegates already hooked up.

These objects still exist in the Xcode 6.1 object library even though iOS 8 deprecates UISearchDisplayController. Unfortunately, at the time of writing, Interface Builder is not able to create the new UISearchController so you must create it in code. First we will add a property to the UYLCountryTableViewController class for the search controller:

@property (strong, nonatomic) UISearchController *searchController;

Now in the viewDidLoad method we can create and setup the search controller.

self.searchController = [[UISearchController alloc]
 initWithSearchResultsController:nil];
self.searchController.searchResultsUpdater = self;
self.searchController.dimsBackgroundDuringPresentation = NO;
self.searchController.searchBar.scopeButtonTitles = 
 @[NSLocalizedString(@"ScopeButtonCountry",@"Country"),
   NSLocalizedString(@"ScopeButtonCapital",@"Capital")];
self.searchController.searchBar.delegate = self;

A few words of explanation for the above code:

  • When creating the search controller we do not need a separate search results controller as we will use the table view controller itself.
  • Likewise we will also use the table view controller to update the search results by having it implement the UISearchResultsUpdating protocol.
  • We do not want to dim the underlying content as we want to show the filtered results as the user types into the search bar.
  • The UISearchController takes care of creating the search bar for us.
  • The table view controller will also act as the search bar delegate for when the user changes the search scope.

Next we add the search bar view to the table view header:

self.tableView.tableHeaderView = self.searchController.searchBar;

Finally since the search view covers the table view when active we make the table view controller define the presentation context:

self.definesPresentationContext = YES;

Finally you must call sizeToFit on the search bar:

[self.searchController.searchBar sizeToFit];

UISearchResultsUpdating Delegate

With the search controller configured the rest is mostly boilerplate code. We need to implement the UISearchResultsUpdating delegate to generate the new filtered results anytime the search text changes:

- (void)updateSearchResultsForSearchController:(UISearchController *)searchController
{
  NSString *searchString = searchController.searchBar.text;
  [self searchForText:searchString scope:searchController.searchBar.selectedScopeButtonIndex];
  [self.tableView reloadData];
}

The -searchForText:scope: method is unchanged so I will not cover it again here.

UISearchBarDelegate - Scope Bar

The UISearchBarDelegate protocol defines optional methods for when the user edits the search text, clicks a search bar button or changes the search bar scope. We are already handling updates to the search results as the user enters the search text using the UISearchResultsUpdating delegate. What is missing is reloading the search results when the user changes the search scope which we can do in searchBar:selectedScopeButtonIndexDidChange:

- (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope
{
  [self updateSearchResultsForSearchController:self.searchController];
}

This method uses the UISearchResultsUpdating delegate method we saw before to update the search results.

Scrolling To Top

There is one more change to the table view controller that while not directly related to the search bar is worth mentioning here. The table view has a section index built in sectionIndexTitlesForTableView: from an array whose first element is the table view index search icon:

NSMutableArray *index = [NSMutableArray arrayWithObject:UITableViewIndexSearch];

In earlier versions of the code the UITableViewDataSource method tableView:sectionForSectionIndexTitle: would force the scroll view to the top of the table by setting the content offset to zero:

self.tableView.contentOffset = CGPointZero;
return NSNotFound;

This no longer works with the new view layout instead we scroll the table view to make the search bar frame visible:

CGRect searchBarFrame = self.searchController.searchBar.frame;
[self.tableView scrollRectToVisible:searchBarFrame animated:NO];
return NSNotFound;

Wrapping Up

I find the new UISearchController interface to be much easier to use compared to the old UISearchDisplayController. I assume Apple will eventually update Interface Builder to support it directly. In the meantime it is trivial to create in code and no longer seems to suffer from the strange and unpredictable bugs of its predecessor.

You can find the updated Xcode project is in my Coding Examples GitHub repository.