Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 414
Brent Traut Avatar asked Jul 26 '16 18:07

Brent Traut


2 Answers

Mark's answer is right. I'd recommend watching WWDC's 2018 Session 225 "A Tour of UICollectionView" to get the full explanation from an Apple engineer.

You can skip to the 33'36" mark for the interesting bit.

Collection View Updates Coalescing Slide

Summary of the video

  • 2 lists: "original items" (before any changes) and "final items" (after all changes);
  • Original indexes → indexes in original items
  • Final indexes → indexes in final items

Operations order in PerformBatchUpdates

  1. Deletes → Always use original indexes (will be used in descending order)
  2. Inserts → Always use final indexes (will be used in ascending order)
  3. Moves → From = original index; To = final index
  4. Reload → Under the hood, it deletes then inserts. Index = original index. You can't reload an item that is moved.

To reload an item that is moved, call all reloads in a separate PerformBatchUpdates, inside a PerformWithoutAnimation (as reloads are never animated).

like image 132
Guillaume Algis Avatar answered Nov 18 '22 20:11

Guillaume Algis


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).

like image 33
Mark Avatar answered Nov 18 '22 21:11

Mark