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 runn
This is my trick:
I set the NSFetchedResultsController's delegate after 'save' method on the NSManagedObjectContext instance is called.
ps. remember to remove that observer if you don't need it anymore
I filed a bug report with Apple back in 2014 on iOS 6/7 about this issue. As many others have noted, it's still a bug on iOS 9 and 10. My original bug report is still open too with no feedback from Apple. Here is an OpenRadar copy of that bug report.
Here's a fix I've used with success but it will get called multiple times. Use with caution.
@objc func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates() // Only needed if you're calling tableView.beginUpdates() in controllerWillChangeContent.
if controller.fetchRequest.fetchLimit > 0 && controller.fetchRequest.fetchLimit < controller.fetchedObjects?.count {
controller.performFetch()
// Reload the table view section here
}
}
}
The problem you have is that you are calling before loading fetchedResultsController charge the full data so it shows you everything you need to do is load all the information and then call fetchedResultsController
Example
- (void)viewDidLoad {
[super viewDidLoad];
// Loading Articles to CoreData
[self loadArticle];
}
- (void)ArticleDidLoadSuccessfully:(NSNotification *)notification {
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort(); // Fail
}
[tableView reloadData];
}
These will still crash in some situations, like several inserts, or move over limit,...
You have to save all the changes to 4 sets, and calculate another 4 arrays and delete/update/insert to tableView before -[UITableView endUpdates]
Some thing like (assume there is only one section):
NSUInteger limit = controller.fetchRequest.fetchLimit;
NSUInteger current = <current section objects count>;
NSMutableArray *inserts = [NSMutableArray array];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"row < %d", limit];
if (insertedIndexPaths.count) {
NSUInteger deletedCount = 0;
for (NSIndexPath *indexPath in insertedIndexPaths) {
if (indexPath.row >= limit) continue;
current++;
if (current > limit) {
deletedCount++;
current--;
[deletedIndexPaths addObject:[NSIndexPath indexPathForRow:limit - deletedCount inSection:indexPath.section]];
}
[inserts addObject:indexPath];
}
}
if (movedIndexPaths.count) {
for (NSIndexPath *indexPath in movedIndexPaths) {
if (indexPath.row >= limit) {
[updatedIndexPaths addObject:[NSIndexPath indexPathForRow:limit - 1 inSection:indexPath.section]];
} else {
[inserts addObject:indexPath];
}
}
}
[updatedIndexPaths minusSet:deletedIndexPaths];
[deletedIndexPaths filterUsingPredicate:predicate];
[updatedIndexPaths filterUsingPredicate:predicate];
[_tableView insertRowsAtIndexPaths:inserts withRowAnimation:UITableViewRowAnimationFade];
[_tableView reloadRowsAtIndexPaths:[updatedIndexPaths allObjects] withRowAnimation:UITableViewRowAnimationNone];
[_tableView deleteRowsAtIndexPaths:[deletedIndexPaths allObjects] withRowAnimation:UITableViewRowAnimationFade];
[_tableView endUpdates];
deletedIndexPaths = nil;
insertedIndexPaths = nil;
updatedIndexPaths = nil;
From apple doc: https://developer.apple.com/reference/coredata/nsfetchrequest/1506622-fetchlimit
If you set a fetch limit, the framework makes a best effort, but does not guarantee, to improve efficiency. For every object store except the SQL store, a fetch request executed with a fetch limit in effect simply performs an unlimited fetch and throws away the unasked for rows.
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;
...
}
}