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:

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:

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;

Update 30-Apr-2015 In some situations it is possible that the size of the UISearchBar does not get set correctly. For example, if you remove the scopeButtonTitles in this example the search bar can end up with a height of zero. The simple workaround is to always call sizeToFit on the scope bar. See this post for more details.

[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.

Never miss a post!

iOS Size Classes Cheat Sheet

Subscribe and get my free iOS Size Classes Cheat Sheet

Success! Now check your email to confirm your subscription and download your free guide to iOS Size Classes.

There was an error submitting your subscription. Please try again.

Unsubscribe at any time.
No time to watch WWDC videos?

Sign up to get my iOS posts direct to your inbox and I will send you a free PDF of my iOS Size Classes Cheat Sheet.

OK! Check your inbox (or spam folder) for an email to confirm your details and download your free guide to iOS Size Classes.

There was an error submitting your subscription. Please try again.

Unsubscribe at any time.
Archives Categories