I'm using UISearchResultsController
to filter data from a fetchedResultsController
. Here is the relevant code:
In RootVieweController.h
:
@interface RootViewController : UITableViewController <NSFetchedResultsControllerDelegate, AdditionViewControllerDelegate, UISearchBarDelegate, UISearchDisplayDelegate> {
NSArray *filteredListContent;
NSString *savedSearchTerm;
NSInteger savedScopeButtonIndex;
BOOL searchIsActive;
}
@property (nonatomic, retain) NSArray *filteredListContent;
@property (nonatomic, copy) NSString *savedSearchTerm;
@property (nonatomic) NSInteger savedScopeButtonIndex;
@property (nonatomic) BOOL searchIsActive;
In RootViewController.m
:
-(void)viewDidLoad {
[snip]
UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 300, 40)];
searchBar.delegate = self;
searchBar.scopeButtonTitles = [NSArray arrayWithObjects:@"Scope 1", @"Scope 2", nil];
[searchBar sizeToFit];
searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
self.tableView.tableHeaderView = searchBar;
[searchBar release];
[self.tableView setContentOffset:CGPointMake(0, 40)];
UISearchDisplayController *searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self];
[self performSelector:@selector(setSearchDisplayController:) withObject:searchDisplayController];
[searchDisplayController setDelegate:self];
[searchDisplayController setSearchResultsDataSource:self];
[searchDisplayController setSearchResultsDelegate:self];
[searchDisplayController release];
self.filteredListContent = [NSMutableArray arrayWithCapacity:[[[self fetchedResultsController] fetchedObjects] count]];
if (self.savedSearchTerm) {
[self.searchDisplayController setActive:self.searchIsActive];
[self.searchDisplayController.searchBar setSelectedScopeButtonIndex:self.savedScopeButtonIndex];
[self.searchDisplayController.searchBar setText:savedSearchTerm];
self.savedSearchTerm = nil;
}
}
- (NSInteger)tableView:(UITableView *)theTableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
if (theTableView == self.searchDisplayController.searchResultsTableView) {
NSLog(@"Search Cells: %i", [self.filteredListContent count]);
return [self.filteredListContent count];
}
id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
NSLog(@"Normal cells: %i", [sectionInfo numberOfObjects]);
return [sectionInfo numberOfObjects];
}
-(void)configureCell:(CustomTableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
//Called from cellForRowAtIndexPath
Object *object = nil;
if (self.searchIsActive) {
object = [[self filteredListContent] objectAtIndex:[indexPath row]];
} else {
object = [fetchedResultsController objectAtIndexPath:indexPath];
}
[snip]
}
#pragma mark -
#pragma mark Search functions
-(void)filterContentForSearchText:(NSString *)searchText scope:(NSString *)scope {
if ([scope isEqualToString:@"Scope 1"]) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"scope1 BEGINSWITH[cd] %@", searchText];
self.filteredListContent = [[[self fetchedResultsController] fetchedObjects] filteredArrayUsingPredicate:predicate];
} else {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"scope2 BEGINSWITH[cd] %@", searchText];
self.filteredListContent = [[[self fetchedResultsController]fetchedObjects] filteredArrayUsingPredicate:predicate];
}
}
#pragma mark -
#pragma mark UISearchDisplayController Delegate Methods
- (void)searchDisplayController:(UISearchDisplayController *)controller willShowSearchResultsTableView:(UITableView *)theTableView {
NSLog(@"Showing search results");
}
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
[self filterContentForSearchText:searchString scope:
[[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:
[self.searchDisplayController.searchBar selectedScopeButtonIndex]]];
NSLog(@"Reloading for string");
return YES;
}
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption {
[self filterContentForSearchText:[self.searchDisplayController.searchBar text] scope:
[[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:searchOption]];
NSLog(@"Reloading for scope");
return YES;
}
-(void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller {
self.searchDisplayController.searchResultsTableView.rowHeight = 55;
self.searchIsActive = YES;
}
-(void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller {
self.searchIsActive = NO;
}
This works great in the simulator. But on a device, it crashes on object = [[self filteredListContent] objectAtIndex:[indexPath row]];
from configureCell
when trying to show the searchResultsTableView. I get the error [NSMutableArray objectAtIndex:]: index 7 beyond bounds for empty array
. Always index 7. What am I missing here?
Fixed: flipped the searchIsActive
BOOL
to switch on searchDisplayControllerDidBeginSearch
instead of searchDisplayControllerWillBeginSearch
and searchDisplayControllerWillEndSearch
instead of searchDisplayControllerDidEndSearch
. That keeps the table from trying to configure cells that don't exist. No clue why the Simulator didn't catch this
If the array it is referencing is empty, it doesn't matter what index it is if you are trying to populate the tableview from an empty array.
Is your NSMutableArray filteredListContent
populated (i.e. are you returning search results from the fetched results controller)? Have you checked the contents of the array in filterContentForSearchText:
?
P.S. I like your style... BeerListTableViewCell
:)