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:
- Animate a bunch of insert, delete, and other operations simultaneously
- "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?