Searching in UITableView

Episode #36 | 15 minutes | published on October 4, 2012
Subscribers Only
Using UISearchDisplayController you can quickly add searching behavior to a UITableView. In this episode we start off with a CoreData model of products, displayed in custom UITableViewCells and add search to filter the products in the table.

Episode Links

The base application

The application starts out with a Core Data model consisting of Products and Categories. The classes are generated using mogenerator.

The products are displayed in a UITableViewController using custom cells.

Adding a Search Bar

First, we want to add a search bar to the header of our table view:

- (void)setupSearchBar {
    self.searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 44)];
    self.tableView.tableHeaderView = self.searchBar;

    // scroll just past the search bar initially    
    CGPoint offset = CGPointMake(0, self.searchBar.frame.size.height);
    self.tableView.contentOffset = offset;

Now we just call this method in viewDidLoad as well as setting up a new NSMutableArray for holding the results of a search.

- (void)viewDidLoad {
    [super viewDidLoad];

    [self setupSearchBar];
    self.searchResults = [NSMutableArray array];

Adding UISearchDisplayController

    // in setupSearchBar
    self.searchController = [[UISearchDisplayController alloc] initWithSearchBar:self.searchBar
    self.searchController.searchResultsDataSource = self;
    self.searchController.searchResultsDelegate = self;
    self.searchController.delegate = self;

Now that our controller is now responding to two different delegate & dataSource we need to update the places where we responding and verify which tableView we are responding for:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (tableView == self.tableView) {
        id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
        return [sectionInfo numberOfObjects];
    } else {
        return self.searchResults.count;

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";

    ProductCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[ProductCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];

    Product *product = nil;
    if (tableView == self.tableView) {
        product = (Product *)[self.fetchedResultsController objectAtIndexPath:indexPath];
    } else {
        product = [self.searchResults objectAtIndex:indexPath.row];

    cell.product = product;

    return cell;

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    if (!self.detailViewController) {
        self.detailViewController = [[DetailViewController alloc] initWithNibName:@"DetailViewController" bundle:nil];

    Product *product = nil;
    if (tableView == self.tableView) {
        product = (Product *)[self.fetchedResultsController objectAtIndexPath:indexPath];
    } else {
        product = [self.searchResults objectAtIndex:indexPath.row];

    self.detailViewController.detailItem = product;
    [self.navigationController pushViewController:self.detailViewController animated:YES];

Filtering the content

Now we need to actually do the searching when the user has typed in a search term. To do this, we simply respond to a delegate method:

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
    [self filterProductsForTerm:searchString];
    return YES;

And then we need to filter the products that match that string, stuffing the results into our array from before:

- (void)filterProductsForTerm:(NSString *)term {
    [self.searchResults removeAllObjects];

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    fetchRequest.entity = [Product entityInManagedObjectContext:[self managedObjectContext]];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name contains[cd] %@ or contains[cd] %@", term, term];
    fetchRequest.predicate = predicate;

    NSError *error = nil;
    NSArray *results = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
    if (error) {
        [NSException raise:NSGenericException format:@"Error filtering for term: %@ -- %@", term, error];

    [self.searchResults addObjectsFromArray:results];

blog comments powered by Disqus