Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use newIndexPath for NSFetchedResultsChangeUpdate?

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:?

like image 345
an0 Avatar asked Sep 15 '12 15:09

an0


2 Answers

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:

  1. A table view has a section at index 0 with 15 items.
  2. The item at index 13 (the second-to-last item) is deleted
  3. The item at index 14 (the last item) is updated

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 indexPathparameter 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.

like image 173
Charles A. Avatar answered Oct 23 '22 18:10

Charles A.


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!

like image 37
Hans One Avatar answered Oct 23 '22 17:10

Hans One