Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSFetchedResultsController ignores fetchLimit?

Tags:

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?

like image 998
marchinram Avatar asked Feb 01 '11 02:02

marchinram


2 Answers

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;         ...     } } 
like image 143
Joseph DeCarlo Avatar answered Oct 05 '22 20:10

Joseph DeCarlo


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.

like image 39
tracy Avatar answered Oct 05 '22 21:10

tracy