Search code examples
iosuitableviewcore-datansfetchedresultscontrollernsfetchrequest

NSFetchedResultsController not refreshing properly after setting new predicate


My fetchedResultsController is initially created like this (in viewDidLoad):

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
                               entityForName:@"Cat" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];

NSSortDescriptor *sort = [[NSSortDescriptor alloc]
                          initWithKey:@"age" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];

NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                    managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"age"
                                               cacheName:@"whatever"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;

This works fine, and results are shown in my table view. However, when pressing certain buttons I want to filter the results. So I do this:

NSPredicate *predicate = ...; //based on buttons pressed


[[self.fetchedResultsController fetchRequest] setPredicate:predicate];
[NSFetchedResultsController deleteCacheWithName:@"whatever"];

NSError * error = nil;
[self.fetchedResultsController performFetch:&error];
if (error) {
    DDLogError(@"[%@ %@] fetchedResultsController ERROR: %@", THIS_FILE, THIS_METHOD, error.localizedDescription);
}
[self.myTableView reloadData];

The problem is that the table view does not reload. If I scroll the table view, the cells change based on new data, but the number of sections and rows does not change as it should each time I try to filter.

What am I missing?


Solution

  • I suspect you need to insert and delete the table view rows based on your updated data.

    The error message you wrote in the comments to the other answer holds the clue.

    Relevant Apple documentation.

    What you should specifically read is the text under the title: Batch Insertion, Deletion, and Reloading of Rows and Sections.

    Before this however, as you are changing your NSFetchedResultsController, you should check that you implement NSFetchedResultsController delegate methods to manage the changes. There is standard code available from Apple that you should consider including in your table view controller. You should not need to change / override this standard code.

    See this Apple documentation.

    Essentially for the "Typical Use" case you will want to implement the four typical delegate methods:

    • (void)controllerWillChangeContent:(NSFetchedResultsController *)controller ;
    • (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo ;
    • (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath ;
    • (void)controllerDidChangeContent:(NSFetchedResultsController *)controller .

    In the case that you've already included these NSFetchedResultsController delegate methods, you may need to consider using the UITableView delegate methods beginUpdates and endUpdates with the methods:

    - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation: (UITableViewRowAnimation)animation;
    - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation: (UITableViewRowAnimation)animation;
    

    So you should try using the following code block:

    tableView.beginUpdates;
    tableView.insertRowsAtIndexPaths:indexPaths withRowAnimation:animation;
    tableView.deleteRowsAtIndexPaths:indexPaths withRowAnimation:animation;
    tableView.endUpdates
    

    You'll obviously need to create the arrays of indexPaths for the insert and delete methods.