NSFetchedResultsController does not start after changing predicate

I have been fixing this problem for several hours, but have not achieved anything so far. I read the whole question here about SO, which may be related to my problem.

I have two entities (A, B) that are connected to each other in a tree structure through a one-to-many relationship: A <--- β†’ B.

There is a UITableViewController supported by NSFetchedResultsController to display all objects of object B associated with the selected object of object A. I use a predicate for this:

- (NSFetchedResultsController *)fetchedResultsController { if (_fetchedResultsController != nil) { return _fetchedResultsController; } NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"B" inManagedObjectContext:self.managedObjectContext]; [fetchRequest setEntity:entity]; [fetchRequest setFetchBatchSize:20]; NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"controlDate" ascending:NO]; NSArray *sortDescriptors = @[sortDescriptor]; [fetchRequest setSortDescriptors:sortDescriptors]; NSMutableArray *subpredicates = [NSMutableArray array]; NSPredicate *predicate1 = [NSPredicate predicateWithFormat:@"isRelatedTo = %@", selectedObjectOfA]; [subpredicates addObject:predicate1]; NSPredicate *predicate2 = [NSPredicate predicateWithFormat:@"syncStatus != %d", ObjectDeleted]; [subpredicates addObject:predicate2]; NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]; [fetchRequest setPredicate:predicate]; NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil]; aFetchedResultsController.delegate = self; self.fetchedResultsController = aFetchedResultsController; NSError *error = nil; if (![self.fetchedResultsController performFetch:&error]) { NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } return _fetchedResultsController; } 

Everything works great for adding, deleting and editing objects. Accordingly, the table is updated. My class corresponds to NSFetchedResultsControllerDelegate, and delegate methods are templates.

But I want the user to be able to cycle through all Entity A objects by pressing the up / down buttons without returning to the navigation stack (for example, you can cycle through email in the Mail application). The number of related objects of object B depends on the selected object A (there is none between them and let's say 20).

And here's the problem: My intention is to update the FRC results by changing the predicate (not the structure or syntax, but only the value of "selectedObjectOfA" and calling

 [self.fetchedResultsController performFetch:&error]; 

Everything else must be done by FRC.

First question: was this approach right?

Here are the scripts that I have already tried:

  • just setting 'selectedObjectOfA' to a new value, and calling the executeFetch function always shows the same result (because fetchRequest and the predicate are not updated, I suppose)

  • 'reset' frc fetchrequest like this

     NSMutableArray *subprediactes = [NSMutableArray array]; NSPredicate *predicate1 = [NSPredicate predicateWithFormat:@"isRelatedTo = %@", self.selectedObjectOfA]; [subprediactes addObject:predicate1]; NSPredicate *predicate2 = [NSPredicate predicateWithFormat:@"syncStatus != %d", ObjectDeleted]; [subprediactes addObject:predicate2]; NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:subprediactes]; [self.fetchedResultsController.fetchRequest setPredicate:predicate]; 

updates FRC and returns the correct data, I suppose, but the application crashes with the wrong number of rows

 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (5), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).' 

because the delegate methods of NSFetchedResultsController are not called. Thus, the number of rows in the View table does not adapt to new results (the number of returned objects corresponds to an error message).

Second question: why does the delegate start when adding / removing objects, but not after the predicates (and therefore the results) have changed?

Thanks in advance.

+7
source share
2 answers

You need to call

 [tableView reloadData]; 

after changing the selection request. The selected result controller only monitors changes that occur after performFetch .

You can try the following to get an animated update to the table view (but I haven't tried it yet, so I'm not sure if it works):

  • Call [tableView beginUpdates] .
  • Use [tableView deleteRowsAtIndexPaths:...] to delete all the "old" rows.
  • Update the query to select the recipient of the results.
  • Call [fetchedResultsController performFetch:...] .
  • Call [tableView insertRowsAtIndexPaths:...] for all objects [fetchedResultsController fetchedObjects] to insert all the "new" rows.
  • Call [tableView endUpdates] .
+3
source

Animating your table view after changing a predicate:

 NSFetchRequest* fetchRequest = self.fetchedResultsController.fetchRequest; fetchRequest.predicate = newPredicate; NSArray* objectsBefore = self.fetchedResultsController.fetchedObjects; [self.fetchedResultsController performFetch:nil]; NSArray* objectsAfter = self.fetchedResultsController.fetchedObjects; [self.tableView beginUpdates]; if (objectsBefore.count > 0) { for (id objectBefore in objectsBefore) { if ([objectsAfter indexOfObject:objectBefore] == NSNotFound) { NSIndexPath* indexPath = [NSIndexPath indexPathForRow:[objectsBefore indexOfObject:objectBefore] inSection:0] ; [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle]; } } } if (objectsAfter.count > 0) { for (id objectAfter in objectsAfter) { if ([objectsBefore indexOfObject:objectAfter] == NSNotFound) { NSIndexPath* indexPath = [NSIndexPath indexPathForRow:[objectsAfter indexOfObject:objectAfter] inSection:0]; [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle]; } } } [self.tableView endUpdates]; 
+4
source

All Articles