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

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


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