Search code examples
iosobjective-cswiftuicollectionviewuikit

How to order moves, inserts, deletes, and updates in a UICollectionView performBatchUpdates block?


In my UICollectionView, I use a simple array of custom objects to produce and display cells. Occasionally that data changes and I'd like to animate the changes all at once. I've chosen to do this by tracking all the changes in a second array, diff'ing the two, and producing a set of move, insert, delete, and update operations inside of a performBatchUpdates block. I now realize it's pretty tricky to do all of these inside the same block because you have to worry about orders of operations with indexes. In fact, the accepted answer to this issue is wrong (but corrected in the comments).

The documentation seems pretty lacking, but it covers one case:

Deletes are processed before inserts in batch operations. This means the indexes for the deletions are processed relative to the indexes of the collection view’s state before the batch operation, and the indexes for the insertions are processed relative to the indexes of the state after all the deletions in the batch operation.

However, the document doesn't talk about when moves are processed. If I call moveItemAtIndexPath and deleteItemsAtIndexPaths in the same performBatchUpdates, should the move indexes be relative to the pre- or post-deleted order? How about insertItemsAtIndexPaths?

Finally, I'm facing issues calling reloadItemsAtIndexPaths and moveItemAtIndexPath in the same operation:

Fatal Exception: NSInternalInconsistencyException attempt to delete and reload the same index path

Is there a way to do all the operations I want in the same performBatchUpdates? If so, what order do the updates get processed relative to the others? If not, what do people usually do? Reload the data after doing all other operations? Before? I'd much prefer if all the animations happened in a single stage.


Solution

  • For the move operations, the from indexPath is pre-delete indices, and the to indexPath is post-delete indices. Reloads should only be specified for indexPaths that have not been inserted, deleted, or moved. This is probably why you're seeing the NSInternalInconsistencyException.

    A handy way to verify the operations are set up correctly: the set of reload, insert and move-to indexPaths should not have any duplicates, and the set of reload, delete, and move-from indexPaths should not have any duplicates.

    UPDATE:

    It appears that items that you move are not also updated, but only moved. So, if you need to update and move an item, you can perform the reload before or after the batch update (depending on the state of your data source).