NSFetchedResultsController - Incomplete UI Update in TableView

I am using NSFetchedResultsController to update data in a table format. The data itself is provided through an XML parser that runs in the background. After the parser has finished, it saves the data in its context. NSFetchedResultsController immediately replaces these changes and starts calling the delegate method -(void)controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: for each updated item. It is also fast and looks normal in log files.

However, in -(void)controllerDidChangeContent: I call UITableView -(void)endUpdates . Then I see the update animation on the screen, but in all the cells next to the last one, which is only half visible, the only thing that is visible is the image on the left side of the cell. All text labels are not visible. It takes 5 to 10 seconds, then all the shortcuts are displayed.

However, if I ignore all calls to the NSFetchedResultsController delegate and just call [self.tableView reloadData] on -(void)controllerDidChangeContent: everything works without problems. Content is right there.

Does anyone know what I'm doing wrong here? The profiler shows that the main thread basically does nothing. Touch events are handled properly, except for events sent to the table view. They are not processed. It seems that the table view is busy with some serious work, but I really don't know what it could be, since the animation has already been done.

Here is my implementation of 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 for my cell location:

 - (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 a pretty basic cell update:

 - (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); } 

Placeholders are also not shown. Labels are completely empty. Only the image is visible!

+6
source share
3 answers

Things I would check first:

  • Make sure your selection is not made in another thread.
  • Make sure your choice is fast, if it is too slow, this may be the reason.
+4
source

Focus on this piece of code:

  case NSFetchedResultsChangeUpdate: [(NewsItemCell*)[tableView cellForRowAtIndexPath:indexPath] updateWithNews:[self.fetchedResultsController objectAtIndexPath:indexPath]]; break; 

A walk through what happens:

  • you open an update transaction on tableView
  • then you respond to delegation of calls by issuing tableView commands
  • but for updates you do it differently by invoking commands not related to the table view
  • then you close the transaction on tableView

I mean: tableView commands can bind changes in one transaction. It looks like your calls for re-cells end in the next transaction. Therefore replace the code above:

 case NSFetchedResultsChangeUpdate: [tableView reloadRowsAtIndexPaths: [NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic ]; break; 
+6
source

Are you calling your NSFetchedResultsControllerDelegate in the background thread? This can be a problem. To check, you can combine the changes of your bg moc to the main moc and watch the main moc instead of bg moc. An NSManagedObjectContext instance must be used by only one thread.

+2
source

Source: https://habr.com/ru/post/927675/


All Articles