17

What is the best practice to filter the NSFetchedResultsController data? do i need to re-initialize it every time the searchbar's text changes?

I am using a UISearchDisplayControllers and i'm implementing:

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString;

Thx.

yanchenko
  • 56,576
  • 33
  • 147
  • 165
Guy
  • 12,488
  • 16
  • 79
  • 119
  • How did you end up handling the table view data source methods so that the table view will Know whether to display the 'filtered list' or not? – CraigH Oct 06 '09 at 20:37
  • 1
    The answer here is very very helpful http://stackoverflow.com/questions/4471289/how-to-filter-nsfetchedresultscontroller-coredata-with-uisearchdisplaycontroll/4481896#4481896 – acecapades Feb 20 '12 at 09:13
  • Here's what I did: http://stackoverflow.com/questions/4471289/how-to-filter-nsfetchedresultscontroller-coredata-with-uisearchdisplaycontrolle/4856118#4856118 – Rob Cohen Jan 31 '11 at 22:06

2 Answers2

17

How is Guy's answer code any different from the question? As far as I can guess, the filterContentForSearchText:scope method is called by the shouldReload methods?

Anyway, here's some similar code that I added in the CoreDataBooks sample to include search. Add a Search Display Controller in IB for the CoreDataBooks example. Then I added code to RootViewController.m as follows:

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
 NSInteger searchOption = controller.searchBar.selectedScopeButtonIndex;
 return [self searchDisplayController:controller shouldReloadTableForSearchString:searchString searchScope:searchOption];
}

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption {
 NSString* searchString = controller.searchBar.text;
 return [self searchDisplayController:controller shouldReloadTableForSearchString:searchString searchScope:searchOption];
}

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString*)searchString searchScope:(NSInteger)searchOption {

 NSPredicate *predicate = nil;
 if ([searchString length])
  if (searchOption == 0) // full text, in my implementation.  Other scope button titles are "Author", "Title"
   predicate = [NSPredicate predicateWithFormat:@"title contains[cd] %@ OR author contains[cd] %@", searchString, searchString];
  else
   // docs say keys are case insensitive, but apparently not so.
   predicate = [NSPredicate predicateWithFormat:@"%K contains[cd] %@", [[controller.searchBar.scopeButtonTitles objectAtIndex:searchOption] lowercaseString], searchString];
 [fetchedResultsController.fetchRequest setPredicate:predicate];

    NSError *error = nil;
    if (![[self fetchedResultsController] performFetch:&error]) {
  NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
  abort();
    }           

 return YES;
}

PS. To answer Vivas, using a UISearchDisplayController it creates a new table view automatically for overlaying the filtered list. You can check which tableView is being used as shown in the docs, but in the simplest setup it just works because the fetchedResultsController is either showing a filtered version in the search's table view or showing all data in your table view.

Vincent Gable
  • 3,455
  • 23
  • 26
dk.
  • 2,030
  • 1
  • 22
  • 22
  • Still strange behavior... objects do get filtered, but when I scroll down the table view it gives an exception, because fetchController doesn't have object at particular index... what can be wrong? please, help – Vladimir Stazhilov Sep 18 '12 at 09:57
  • I figured out my problem... when I get filtered results they are displayed in the tableView configured in the same way and when I scroll to row number X where the real number of rows is smaller than X... what should I do.. – Vladimir Stazhilov Sep 18 '12 at 11:14
9

Appearantly this is a better way:

- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
    self.savedSearchTerm = searchText;

    freshData = NO;
    if (searchText !=nil)
    {
            NSPredicate *predicate =[NSPredicate predicateWithFormat:@"name contains[cd] %@", searchText];
            [fetchedResultsController.fetchRequest setPredicate:predicate];
    }
    else
    {
            NSPredicate *predicate =[NSPredicate predicateWithFormat:@"All"];
            [fetchedResultsController.fetchRequest setPredicate:predicate];
    }

    NSError *error = nil;
    if (![[self fetchedResultsController] performFetch:&error]) {
            // Handle error
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            exit(-1);  // Fail
    }           

    [self.tableView reloadData];

    //    [searchBar resignFirstResponder];   
    //    [_shadeView setAlpha:0.0f];

 }
Alex Reynolds
  • 95,983
  • 54
  • 240
  • 345
Guy
  • 12,488
  • 16
  • 79
  • 119
  • 5
    The appl docs say that the fetch request should not be altered - they specifically say that you should not change the predicate. – Rog Nov 07 '10 at 18:15
  • 1
    @RogerNolan Are you sure? Check out [NSFetchedResultsController Class Reference : Modifying the Fetch Request](http://j.mp/z3W7QK). Those three steps work fine for me, and I even modify the fetch request's predicate. – ma11hew28 Jan 21 '12 at 22:49
  • This is a change (I think). Either way, the instructions are basically that if you change the request, you have to reissue the search. – Rog Jan 23 '12 at 15:39
  • I have edited the sample to call abort() instead of exit(-1). As 0xced said, maybe neither should be done, but it's *never* a good idea to call exit() on iOS; abort() is preferred for debugging . – Vincent Gable Apr 19 '12 at 23:36
  • This works with only if you are searching in existing tableview. Apart form that this is a great solution with a very minimal code change. Amazing bro. Thumbs up – user431791 Jul 10 '15 at 09:57