20
votes

I have an array of arrays that I'm using for the data source of a table view. In one instance of time, I may have to do some complicated modifications to this data structure. (For example, a sequence of operations I may need to do is: delete a row here, insert a row there, insert a section here, delete another row, insert another row, delete another row, insert another section--you get the idea.) This is easy to do if, for each operation in the sequence, I just update the data source and then do the corresponding update for the table view immediately. In other words, the pseudocode will look like:

[arrayOfArrays updateForOperation1];
[tableView updateForOperation1];

[arrayOfArrays updateForOperation2];
[tableView updateForOperation2];

[arrayOfArrays updateForOperation3];
[tableView updateForOperation3];

[arrayOfArrays updateForOperation4];
[tableView updateForOperation4];

// Etc.

However, if I were to surround these operations in a beginUpdates/endUpdates "block," this code does not work anymore. To see why, imaging beginning with an empty table view and inserting four rows in turn at the beginning of the first section. Here's the pseudocode:

[tableView beginUpdates];

[arrayOfArrays insertRowAtIndex:0];
[tableView insertRowAtIndexPath:[row 0, section 0]];

[arrayOfArrays insertRowAtIndex:0];
[tableView insertRowAtIndexPath:[row 0, section 0]];

[arrayOfArrays insertRowAtIndex:0];
[tableView insertRowAtIndexPath:[row 0, section 0]];

[arrayOfArrays insertRowAtIndex:0];
[tableView insertRowAtIndexPath:[row 0, section 0]];

[tableView endUpdates];

When endUpdates gets called, the table view discovers that you are inserting four rows all colliding at row 0!

If we REALLY want to keep the beginUpdates/endUpdates part of the code, we have to do something complicated. (1) We do all the updates to the data source without updating the table view as we go along. (2) We figure out how the parts of the data source before all the updates maps to the parts of the data source after all the updates to figure out what updates we need to perform for the table view. (3) Finally, update the table view. The pseudocode looks something like this to accomplish what we were trying to do in the previous example:

oldArrayOfArrays = [self recordStateOfArrayOfArrays];

// Step 1:
[arrayOfArrays insertRowAtIndex:0];
[arrayOfArrays insertRowAtIndex:0];
[arrayOfArrays insertRowAtIndex:0];
[arrayOfArrays insertRowAtIndex:0];

// Step 2:
// Comparing the old and new version of arrayOfArrays,
// we find we need to insert these index paths:
// @[[row 0, section 0],
//   [row 1, section 0],
//   [row 2, section 0],
//   [row 3, section 0]];
indexPathsToInsert = [self compareOldAndNewArrayOfArraysToGetIndexPathsToInsert];

// Step 3:

[tableView beginUpdates];

for (indexPath in indexPathsToInsert) {
    [tableView insertIndexPath:indexPath];
}

[tableView endUpdates];

Why do all this for the sake of beginUpdates/endUpdates? The documentation says to use beginUpdates and endUpdates to do two things:

  1. Animate a bunch of insert, delete, and other operations simultaneously
  2. "If you do not make the insertion, deletion, and selection calls inside this block, table attributes such as row count might become invalid." (What does this really mean exactly anyway?)

However, if I don't use beginUpdates/endUpdates, the table view looks like it's animating the various changes simultaneously, and I don't think the table view's internal consistency is being corrupted. So what is the benefit of doing the complicated approach with the beginUpdates/endUpdates?

1
The Table View Programming Guide for iOS covers this quite clearly. See the section on Batch Insertion, Deletion, and Reloadingrmaddy
@rmaddy So I see that beginUpdates/endUpdates define an animation block (as well a grouping of inserts/deletes so that all deletes happen before all inserts). It seems like what I should do in my case is to do what I outlined in the first pseudocode listing of my question (i.e., not use beginUpdates/endUpdates). However, what I'm afraid of with this is that each insert/delete is like its own call to [UIView animationWithDuration:...], so the next call will cancel the animations ordered in the previous call. Or is UITableView smart enough to animate the inserts/deletes in one animation block?ememem
I'm having the same problem, and this hasn't really been answered... So did you end up not calling the beginUpdates/endUpdates? It seems to be working fine in my app, but I'm afraid I'm missing something. On a side note, NSTableView doesn't have this "problem", the order of inserts and deletes matter.pfandrade

1 Answers

22
votes

Every time you add/remove a table item the tableView:numberOfRowsInSection: method is called - unless you surround these calls with begin/endUpdate. If your array and table view items are out of sync, without the begin/end calls an exception will be thrown.