views:

798

answers:

1

The docs for NSFetchedResultsControllerDelegate provide the following sample code

- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath {

    UITableView *tableView = self.tableView;

    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 configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
            break;

        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            [tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
            break;

    }

}

When I create a new NSManagedObject, NSFetchedResultsChangeInsert fires (great!). When I change the value of an attribute (used for the cell's title), the NSFetchedResultsChangeUpdate fires. Unfortunately, the new title doesn't automatically display unless I reload the table, section or row. Indeed, if the new name causes the result set to sort differently, then NSFetchedResultsChangeMove fires and all is well since the provided code reloads the entire section.

UITableView has a method reloadRowsAtIndexPaths:withRowAnimation so I tried using this under the NSFetchedResultsChangeUpdate code block. It does indeed work ... but the docs for this specific method read as though I don't need it (notice the last line):

Reloading a row causes the table view to ask its data source for a new cell for that row. The table animates that new cell in as it animates the old row out. Call this method if you want to alert the user that the value of a cell is changing. If, however, notifying the user is not important—that is, you just want to change the value that a cell is displaying—you can get the cell for a particular row and set its new value.

And yes, if I log what is happening, when

[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];

gets invoked on an NSFetchedResultsChangeUpdate, it is able to retrieve the latest 'name' value and set it in the cell's textLabel. The name is just not rendering in the cell unless I reload it. Even if I simply click the cell the name shows up. Note that to recreate this behavior, you must create a new managed object and then give it a name that causes it to sort FIRST in the NSFetchedResultsController. That way, the NSFetchedResultsChangeMove doesn't fire (which does work since it reloads the section).

Am I missing something or is this expected behavior? The 'discussion' for reloadRowsAtIndexPaths leads me to believe I should be able to simply set the cell's textLabel without reloading the row, section or table.

+1  A: 

While it's true that you do not need to reload the cell in order to have the changes take place, you have to remember that the iPhone caches view drawing as much as possible. Once you have newly configured your cell, you need to call setNeedsDisplay on the cell in order to trigger the redraw.

Douglas Mayle
At this point - what is the best way to get the cell that is on the screen? tableview:cellForRowAtIndexPath: creates a new cell whereas I want to get a reference to the cell on-screen ... right?
Luther Baker
Normally, there's no need, for any cell that you're configuring, you can call setNeedsDisplay. If it's a new cell, it doesn't have a cache, so no worries, if you're reconfiguring, it will redraw. If you really want to know which screens are visible, however, take a look at my response to: http://stackoverflow.com/questions/996515/getting-visible-cell-from-uitableview-pagingenabled/1566432#1566432
Douglas Mayle
So, I've got 8 cells displayed on a screen. I update the underlying value for the 'nth' cell. That trips controller:didChangeObject: ... where I handle case NSFetchedResultsChangeUpdate. It is from this case block that I want to update the onscreen cell. I don't have the CELL ... so I can't simply call [cell setNeedsDisplay]. And the only way to get it is to use tableview:cellForRowAtIndexPath --- but that doesn't get the onscreen cell, that creates a new one. Invoking that doesn't replace anything - it creates a new cell.
Luther Baker
So, I end up invoking reloadRowsAtIndexPaths .... and the cell redraws correctly. Is there a backdoor approach to actually get a reference to the cell view currently on screen and setNeedsDisplay on such that I can avoid invoking reloadRowsAtIndexPaths?
Luther Baker