Search code examples
iphoneobjective-cuisearchbarnsfetchedresultscontrolleruisearchdisplaycontroller

UISearchResultsController crashing on device (index beyond bounds for empty array)


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?

--UPDATE--

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


Solution

  • 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 :)