Search code examples
iphoneiosuitableviewnsfetchedresultscontroller

Scroll to last UITableViewCell using scrollToRowAtIndexPath:atScrollPosition:animated using NSFetchedResultsControllerDelegate methods


I am adding a new item to the bottom of a UITableView and after inserting the item, I want the UITableView to scroll to the very bottom to display the newly inserted item. New items are saved to Core Data and the UITableView is automatically updated using NSFetchedResultsController.

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
   atIndexPath:(NSIndexPath *)indexPath
 forChangeType:(NSFetchedResultsChangeType)type
  newIndexPath:(NSIndexPath *)newIndexPath
{
  switch (type) {
    case NSFetchedResultsChangeInsert:
        NSLog(@"*** controllerDidChangeObject - NSFetchedResultsChangeInsert");
        [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];

    //THIS IS THE CODE THAT DOESN'T WORK
    [self.tableView scrollToRowAtIndexPath:newIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];

        break;

   ....
}

This leads to an out of bounds error, I can't seem to make it work. I can scroll to the second to last comment by adjusting the row of the index path, but I can't get to the very last item.

Basically, I'm adding a comment to a table of comments and after a comment is added, I want the table to scroll to the most recent comment.


Solution

  • You need to call endUpdates so that the tableView can calculate its new sections and rows. A simple case would look like this:

    [self.tableView beginUpdates];
    [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:insertedIndexPath] withRowAnimation:UITableViewRowAnimationFade];
    [self.tableView endUpdates];
    [self.tableView scrollToRowAtIndexPath:insertedIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
    

    As you use NSFetchedResultsController, it is a bit more complicated, as the calls do beginUpdates, insertRowsAtIndexPaths:withRowAnimation:, and endUpdates are typically in different delegate methods. What you could do then is

    1. add a property insertedIndexPath to store the inserted index path
    2. after the -insertRowsAtIndexPaths:withRowAnimation: call in -controller:didChangeObject:atIndexPath:, add

      self.insertedIndexPath = insertedIndexPath;
      
    3. after [self.tableView endUpdates] in -controllerDidChangeContent:, add

      if (self.insertedIndexPath) {
          [self.tableView scrollToRowAtIndexPath:self.insertedIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
          self.insertedIndexPath = nil;
      }