Ian Henry
Ian Henry

Reputation: 22403

UITableView's reloadRowsAtIndexPaths: seems to break insertRowsAtIndexPaths:

I have a UITableView into which the user can insert new rows. When this happens, I want to reload all of the old rows in the table. One solution would be to just call reloadData as soon as the insertion takes place, which totally works, but this means I don't get the insertion animation.

So when the user hits the "add row" button, I call reloadRowsAtIndexPaths: with every index path except the one just inserted. Then I call insertRowsAtIndexPaths: with only the newly inserted row. Reasonable, right?

This causes the app to crash with the following explanation:

Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (0), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).

This happens, as you can see in this example, even when reloadRowsAtIndexPaths is passed an empty array of index paths.

Ah! I need to wrap the two calls with beginUpdates and endUpdates. Fair enough. But now the animation is completely broken.

I'm performing the reload with a UITableViewRowAnimationFade and the insertion with a UITableViewRowAnimationAutomatic. But during the animation, the heights of every row changes, creating this weird flickery effect that looks just terrible. What's the correct way to animate these changes?

Edit:

From the docs for reloadRowsAtIndexPaths:withRowAnimation::

Reloading a row causes the table view to ask its data source for a new cell for that row. The table animates that new cell in as it animates the old row out. Call this method if you want to alert the user that the value of a cell is changing. If, however, notifying the user is not important—that is, you just want to change the value that a cell is displaying—you can get the cell for a particular row and set its new value.

I think that, in my application, manually updating each cell is the right way to go. However, I am still perturbed by this bizarre animation bug, and would like to know what the cause of it is / what I would do if I did "want to alert the user that the value of the cell is changing."

Upvotes: 3

Views: 4032

Answers (2)

Matt
Matt

Reputation: 1586

A common reason for getting that error is, as the docs say, the datasource is asked for a cell. It might be less clear that it is asked twice...once to provide the initial data, again to provide the final data. That implies that the tableView:numberOfRowsInSection: method must return the old values before beginUpdates and the new values after endUpdates (or if you're using a shortcut method, before and after the call). Don't forget about numberOfSectionsInTableView either if it is relevant.

Example:

    numberOfRows... return [array count];

    // Incorrect
    [array addObject:object];
    [tableView beginUpdates];
    [tableView insertRow..];
    [tableView endUpdates];

    // Correct
    [tableView beginUpdates];
    [array addObject:object];
    [tableView insertRow..];
    [tableView endUpdates];

For your specific case, I would recommend doing the insertion, then calling reloadData on the whole tableview so long as that doesn't mess up your animation or anything else.

Upvotes: 4

Aft3rmath
Aft3rmath

Reputation: 669

You don't need to have any operations with exactly cells. All what you need - is change your datasource array, then call reload data/row/section, and you'll get changed data in your table view, with added rows.

Upvotes: -2

Related Questions