Episode #36

Searching in UITableView

15 minutes
Published on October 4, 2012

This video is only available to subscribers. Get access to this video and 572 others.

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
                                                              contentsController:self];
    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 category.name 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];
}