I thought I got it. But a new crash I found in my app says otherwise. So any one knows the really correct code for NSFetchedResultsChangeUpdate
when newIndexPath is non-nil and not the same as indexPath in -controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:
?
I just ran into a crash on update, and it appears that the newIndexPath
is provided as an index path into the fetched results controller objects when that index path does not match the index path needed to retrieve the cell from the table. Consider the following:
In the above case, assuming that you are using [tableView beginUpdates]
and [tableView endUpdates]
in the appropriate controllerWill/DidChangeContent:
methods, you will need to use the indexPath
parameter to retrieve the cell from the table to update (which will be section 0, index 14) and the newIndexPath
parameter to retrieve the object to configure the cell with from the results controller (which will be section 0, index 13).
I assume it works this way because the delete seems to have already happened as far as the results controller is concerned, but has not happened in the table view (due to the beginUpdates/endUpdates
calls wrapping the updates). It makes some sense if you consider the above case, but it seems that all of the documentation does not consider this case.
So the answer to the question is that it appears that you should use the indexPath
parameter to retrieve a cell from the table view, and the newIndexPath
parameter to retrieve the object from the fetched results controller. Note that if there has not been an insert or delete it appears to pass nil
for newIndexPath
, so in this case you would have to use indexPath
for both purposes.
If an object of NSFetchedResultsController changes and moves at the "same time" -controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:
sends type NSFetchedResultsChangeUpdate
(see here).
My solution is to change the type to move everytime the type is update and indexPath is not equal to newIndexPath
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView;
if (controller == self.fetchedResultsController) {
tableView = self.tableView;
}
else {
tableView = self.searchDisplayController.searchResultsTableView;
}
// type is "update" ---> should be "move"
if (type == NSFetchedResultsChangeUpdate && [indexPath compare:newIndexPath] != NSOrderedSame && newIndexPath != nil) {
type = NSFetchedResultsChangeMove;
}
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 fetchedResultsController:controller configureCell:(UITableViewCell*)[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationRight];
break;
}
}
Afterwards you have to update the table view
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
[self.tableView reloadData];
}
I hope this is helpful!
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