I have implemented an NSFetchedResultsController
delegate with NSManagedContextDidSaveNotification
to push managed objects changes from another NSManagedObjectContext
connected to a common NSPersistentStoreCoordinator
.
When the managed objects are first batch imported and after the NSFetchedResultsControllerDelegate
methods are called, the result of the cell drawing contains a section that looks like this:
It is basically a display/drawing bug where a section header gets drawn into a cell. That header and cell are actual valid header and cell that appear further down.
It only happens the first time managed objects are created in batch. If I restart the app and the managed objects are already imported the controller displays everything fine so it's likely something to do with the import process, which is just the typical NSFetchedResultsControllerDelegate
implementation (pasted below):
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[_tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:
(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
[_tableView insertSections:
[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade]; break;
case NSFetchedResultsChangeDelete:
[_tableView deleteSections:
[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade]; break;
case NSFetchedResultsChangeUpdate:
NSLog(@"change section"); break;
case NSFetchedResultsChangeMove:
NSLog(@"move setion"); break;
}
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject atIndexPath:
(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
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];
NSLog(@"change object"); break;
case NSFetchedResultsChangeMove:
[_tableView deleteRowsAtIndexPaths:
[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[_tableView insertRowsAtIndexPaths:
[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
NSLog(@"move object"); break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[_tableView endUpdates];
}
The thing is I tried just implementing a refresh with needsLayout
and reloadData
by detecting when it's a first batch import and calling them doesn't get rid of this display issue. HALP!
EDIT:
- (void)setupFetchedResultsController {
NSFetchRequest *fr = [NSFetchRequest fetchRequestWithEntityName:@"Object"];
NSSortDescriptor *sd1 = [NSSortDescriptor sortDescriptorWithKey:@"attribute" ascending:YES];
NSSortDescriptor *sd2 = [NSSortDescriptor sortDescriptorWithKey:@"attribute2" ascending:YES];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self.relationship.attribute3 == true"];
[fr setPredicate:predicate];
[fr setSortDescriptors:@[sd1, sd2]];
_frc = [[NSFetchedResultsController alloc] initWithFetchRequest:fr managedObjectContext:_moc sectionNameKeyPath:@"attribute" cacheName:nil];
[_frc setDelegate:self];
NSError *error; if (![_frc performFetch:&error]) NSLog(@"%@", error.description);
}
EDIT: Here is the code that fixed the issue:
- (void)subscribeToNotifications {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(mergeManagedObject:)
name:NSManagedObjectContextDidSaveNotification
object:nil];
}
- (void)mergeManagedObject:(NSNotification *)notification {
dispatch_async(dispatch_get_main_queue(), ^{
// This block needed to be sent on the main thread!
[_managedObjectContext
mergeChangesFromContextDidSaveNotification:notification];
});
}
Did you try implementing/adjusting your code as follows?
- reloadSections:withRowAnimation:
when NSFetchedResultsChangeUpdate
on section changes,- moveSection:toSection:
when NSFetchedResultsChangeMove
happens on section changes,- moveRowAtIndexPath:toIndexPath:
when NSFetchedResultsChangeMove
is triggered on controller:didChangeObject:
, and- reloadRowsAtIndexPaths:withRowAnimation:
when NSFetchedResultsChangeUpdate
is triggered on controller:didChangeObject:
.If that doesn't help, step into those methods and look on what thread they're called. Often, view bugs happen when view updates are called on threads other than the main thread.
Last, did you try using childContexts?