I have a NSFetchedResultsController to update a UITableView with content from Core Data. It's pretty standard stuff I'm sure you've all seen many times however I am running into slight problem. First here's my code:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"Article" inManagedObjectContext:self.managedObjectContext]; [fetchRequest setEntity:entity]; [fetchRequest setFetchLimit:20]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(folder.hidden == NO)"]; [fetchRequest setPredicate:predicate]; NSSortDescriptor *sort1 = [NSSortDescriptor sortDescriptorWithKey:@"sortDate" ascending:NO]; [fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sort1, nil]]; NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil]; [fetchRequest release]; controller.delegate = self; self.fetchedResultsController = controller; [controller release]; NSError *error = nil; [self.fetchedResultsController performFetch:&error]; if (error) { // TODO send error notification NSLog(@"%@", [error localizedDescription]); }
The problem is that initially the store has no entities as it downloads and syncs from a webservice. What happens is that the NSFetchedResultsController fills the table with over 150 rows of entities from the store, which is how many the webservice returns. But I am setting a fetch limit of 20 which it appears to be ignoring. However, if I close out the app and start again with data already in the store, it works fine. Im my delegate i do this:
#pragma mark - #pragma mark NSFetchedResultsControllerDelegate methods - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { [self.tableView beginUpdates]; } - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { switch(type) { case NSFetchedResultsChangeInsert: [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; } } - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { UITableView *tableView = self.tableView; switch(type) { case NSFetchedResultsChangeInsert: [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeUpdate: [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath]; break; case NSFetchedResultsChangeMove: [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; break; } } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { [self.tableView endUpdates]; }
Which is pretty much copy-paste from Apple's dev documents, any ideas what's goin on?
I know this is an old question, but I have a solution for it:
Since there is a known bug in NSFetchedResultsController
that doesn't honor the fetchlimit
of the NSFetchRequest
, you have to manually handle the limiting of records within your UITableViewDataSource
and NSFetchedResultsControllerDelegate
methods.
tableView:numberOfRowsInSection:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section]; NSInteger numRows = [sectionInfo numberOfObjects]; if (numRows > self.fetchedResultsController.fetchRequest.fetchLimit) { numRows = self.fetchedResultsController.fetchRequest.fetchLimit; } return numRows; }
controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { switch(type) { case NSFetchedResultsChangeInsert: if ([self.tableView numberOfRowsInSection:0] == self.fetchedResultsController.fetchRequest.fetchLimit) { //Determining which row to delete depends on your sort descriptors [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:self.fetchedResultsController.fetchRequest.fetchLimit - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationFade]; } [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; break; ... } }
This is an old question but I just ran into it myself (in iOS 5). I think you're running into the bug described here: https://devforums.apple.com/message/279576#279576.
That thread provides solutions based on whether you have a sectionNameKeyPath or not. Since I (like you) didn't, the answer is to decouple the tableview from the fetchedResultsController. For example, instead of using it to determine the number of rows:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [[[self.fetchedResultsController sections] objectAtIndex:0] numberOfObjects];
just return what you expect:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return fetchLimit;
And in controller:didChangeObject
, only insert the new object if the newIndexPath is within your fetchLimit.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With