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!
Let's focus on this piece of code:
case NSFetchedResultsChangeUpdate:
[(NewsItemCell*)[tableView cellForRowAtIndexPath:indexPath] updateWithNews:[self.fetchedResultsController objectAtIndexPath:indexPath]];
break;
To walk you through what's happening:
What I mean is: tableView commands may bundle the changes into one transaction. Seems like your calls to rerender cells are ending up on the next transaction. So replace the code above with:
case NSFetchedResultsChangeUpdate:
[tableView reloadRowsAtIndexPaths: [NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic ];
break;
Things I would check first:
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