I am using an NSFetchedResultsController to refresh the data of a table view. The data itself is provided via an XML parser that runs on the background. After the parser finished, it saves the data into its own context. The NSFetchedResultsController picks up these changes immediately and starts calling the -(void)controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: delegate method for each updated element. This also is fast and looks totally normal in the log files.
However, in -(void)controllerDidChangeContent: I call UITableView‘s -(void)endUpdates. Then I see the update animation on the screen, but in all cells, beside the last one which is only half visible, the only thing that is visible is an image on the left side of the cell. All text labels are not visible. It takes about 5 to 10 seconds, then all the labels pop visible.
However if I ignore all the delegate calls of the NSFetchedResultsController and simply call [self.tableView reloadData] on -(void)controllerDidChangeContent: everything works without problems. The content is there immediately.
Has anybody an idea what I am doing wrong here? The profiler shows that the main thread is basically doing nothing. Touch events are handled properly, besides the events that are dispatched to the table view. These aren’t handled. It seems like the table view is busy doing some serious work, but I really don’t know what that could be, as the animation is already done.
Here is my implementation of the NSFetchedResultsControllerDelegate:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
NSLog(@"%s", __PRETTY_FUNCTION__);
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
NSLog(@"%s", __PRETTY_FUNCTION__);
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath*)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath*)newIndexPath {
NSLog(@"%s", __PRETTY_FUNCTION__);
UITableView* tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeUpdate:
[(NewsItemCell*)[tableView cellForRowAtIndexPath:indexPath] updateWithNews:[self.fetchedResultsController objectAtIndexPath:indexPath]];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
NSLog(@"%s", __PRETTY_FUNCTION__);
[self.tableView endUpdates];
}
And this is the code of my cell layout:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return self.fetchedResultsController.sections.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id<NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController.sections objectAtIndex:section];
return sectionInfo.numberOfObjects;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
News* model = [self.fetchedResultsController objectAtIndexPath:indexPath];
NewsItemCell* cell = (NewsItemCell*)[tableView dequeueReusableCellWithIdentifier:NewsCellReuseIdentifier];
[cell updateWithNews:model];
cell.accessoryType = (model.content ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone);
return cell;
}
And the pretty basic update of the cell:
- (void)updateWithNews:(News*)news {
NSString* dateString = [[NSDateFormatter outputDateFormatter] stringFromDate:news.date];
self.headlineLabel.text = (news.headline ? news.headline : NSLocalizedString(@"<NewsNoHeadlineReplacement>", nil));
self.metaInfoLabel.text = [NSString stringWithFormat:NSLocalizedString(@"<NewsMetaInfoFormatDate>", nil), (dateString ? dateString : (NSLocalizedString(@"<NewsNoDateReplacement>", nil)))];
self.readIndicatorView.hidden = (news.read != nil && [news.read compare:news.parsingDate] == NSOrderedDescending);
}
The placeholder strings aren’t shown either. The labels are completely empty. Only the image is visible!
Things I would check first: