Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITableView insertSections: withRowAnimation: causing all table cells to be requested

I'm working on modifying some existing heavy-handed code that simply calls [tableView reloadData] on any change, to using more specific table updates with the insert/delete methods.

However, I'm getting some really bad behavior in doing so. Previously, as one would imagine, when the table loaded, it only requested cells for the rows that were visible at the time. This was the behavior when reloadData was used.

Now that insertSections is being called, all cells are requested after that update, which can be hundreds. This results in cells being created for every row, completely ruining the reusable cell queue and just being all around wasteful. I must be doing something wrong.

The change is this simple, code that results in the tableView asking only for visible rows:

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
    // ... ensure it's the right key
    [tableView reloadData];
}

Code that results in the tableView asking for everything:

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
    // ... ensure it's the right key
    NSUInteger sectionCount = [self sectionCount];
    NSIndexSet *indices = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)];
    [tableView insertSections:indices withRowAnimation:UITableViewRowAnimationFade];
}

I can toggle back and forth to see the behavior change. Frustrating. Ideas?


Adding a bounty just to see if anyone has any more insight.

The beginUpdates/endUpdates doesn't affect anything, and I wouldn't expect it to, this is just one command, there's nothing extra to coalesce into a single update.

I'm thinking this is simply a side effect of desiring the animation. To have everything "slide" in, it has to have everything to render. Game over.

like image 780
Nick Veys Avatar asked Jul 25 '09 04:07

Nick Veys


1 Answers

It appears you are telling the table to insert all of the sections, which is basically equivalent to a reload. When you tell the table it needs to load a section it needs to read all the items in that section. Also, you are doing it outside of a beginUpdates/endUpdates block, which means every time you add in the section the table has to immediately commit the changes. If you wrap everything inside of a beginUpdates/endUpdates it will suspend all the queries until it is done which will allow the system to coalesce redundant queries and eliminate queries that turn out not to be necessary.

You should only be calling insertSections with new sections that are added. If you do that then tableview does not have to query the information of all the unaltered sections, even if they move around. Also, if elements inside an section change but the section itself doesn't you should use the row version of the methods to modify those specific rows inside of the section, and only those rows will get queried.

Below is an example (from a CoreData based app) that should get the point of across. Since you have a custom model instead an NSFetchedResultsController you will have to figure out the type of action going on instead of just checking a bunch of constants handed to you by the system.

//Calculate what needs to be changed     

[self.tableView beginUpdates];

for (i = 0 i < changeCount; i++) {
  type = changes[i]; 

  switch(type) {
    case NSFetchedResultsChangeInsert:
      [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                    withRowAnimation:UITableViewRowAnimationFade];
      break;

    case NSFetchedResultsChangeDelete:
      [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                    withRowAnimation:UITableViewRowAnimationFade];
      break;
  }
}

switch(type) {

  case NSFetchedResultsChangeInsert:
    [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                          withRowAnimation:UITableViewRowAnimationFade];
    break;

  case NSFetchedResultsChangeDelete:
    [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                          withRowAnimation:UITableViewRowAnimationFade];
    break;

  case NSFetchedResultsChangeUpdate:
    [self configureCell:(id)[tableView cellForRowAtIndexPath:indexPath]
            atIndexPath:indexPath];
    break;

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

[self.tableView endUpdates];
like image 160
Louis Gerbarg Avatar answered Oct 11 '22 19:10

Louis Gerbarg