Recently I have been working on some iPhone applications. I have strong opinions on the process of developing in the XCode environment, using Interface Builder, the poor development Mac tools and the general coding pain I have been going through, but that is a post for another day. What I wanted to focus on here is a problem I just resolved with the UITableViewController class.

For those not familiar with Mac’s Cocoa programming, a standard way of displaying lists of information is to use the ubiquitous UITableViewController class. It is a controller class provides support for displaying data in a table. It seems pretty straight forward, but alas, it has its pitfalls.

The UITableViewController class allows you to add items and (optionally) sections to the table. For example if you wanted to add the names of your friends, you could add them one at a time. You would then have a 0 based index of your friends. And if you wanted to group them (let’s say into male and female) you would have a 0 based index of gender types, each with a 0 based list of friend names.

Now the table cells only know their position in the table (section and row within the section). When the table view is displayed on the screen the UITableViewController class calls into your own code for each cell to determine any data to display. The table will pass into your code the section index and row-within-the-section index and expect you to figure out what data that maps to.

This means that on the back end you need to keep a 0 based index list of sections an rows for each of the sections. All in all this isn’t difficult — just a bit of effort to keep everything in sync. But, there is a hidden problem lurking just around the corner…bulk editing!

When you have lots of edits to make (add a few rows, delete a section, insert sections and rows) you can bulk them up for speed and to keep the UI from showing cells and sections dancing around like an insane program. This is done by calling the table view’s beginUpdates and endUpdates. Once you call endUpdates then the UI will simply show a single transition from before changes to the end result. It actually does look nice.

Okay, so what is the problem? Well, the problem is that when changes are made the table tries to determine how many sections and rows per section there are. To do this it calls into your code where you will probably could the number of sections in your section list and number of rows in your section-rows lists. If the changes are made before you update these lists then the number of sections/rows you report back to the table will differ from the bulk changes you submitted. This will lead to an assertion failure.

For example, let’s say you have this code:
NSMutableIndexSet *setSectionsDelete = [[NSMutableIndexSet alloc] init];
for( int iSection = 0; iSection < [self.tableView numberOfSections]; iSection++ )
{
  [setSectionsDelete addIndex:iSection];
}

// Do the bulk removal of sections
[self.tableView beginUpdates];
[self.tableView deleteSections:setSectionsDelete withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];

[arraySections removeAllObjects];
[dictSections removeAllObjects];

Let’s assume that you already have defined arraySections as an NSArray object with the names of all sections (this is your 0 based index) and dictSections as an NSDictionary with keys for each section name and values which are NSArrays for rows. When the code executes [self.tableView endUpdates] it will assert with:

Assertion failure in -[UITableView _endCellAnimationsWithContext:]

and an unhandled exception will occur, something like:

‘NSInternalInconsistencyException’, reason: ‘Invalid update: invalid number of rows in section 2. The number of rows contained in an existing section after the update (4) must be equal to the number of rows contained in that section before the update (4), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted).’

The problem was that the code made the bulk section removals without first updating the arraySections and dictSections objects. While the table view is processing your bulk edits it will call into your code’s numberOfSectionsInTableView: method to check that the number of sections are correct. If your code is something like:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
  return( [dictSections count] );
}

…it will return the count of keys in the dictionary (in my example the keys represent the sections). If I have not cleared out the dictionary before the bulk editing then the method will return a value not based on your table edits, but based on the yet-to-be-cleared lists and you will get this error.

A better way to manage this problem is to simply rewrite the code to generate the list of sections then clear out the lists followed by the bulk edit:
NSMutableIndexSet *setSectionsDelete = [[NSMutableIndexSet alloc] init];
for( int iSection = 0; iSection < [self.tableView numberOfSections]; iSection++ )
{
  [setSectionsDelete addIndex:iSection];
}
[arraySections removeAllObjects];
[dictSections removeAllObjects];

// Do the bulk removal of sections
[self.tableView beginUpdates];
[self.tableView deleteSections:setSectionsDelete withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];

I do hope that this helps someone. I have been tracking down this problem for several hours and it has been driving me crazy.