UICollectionView batch move items crashing with ghost insert

`UICollectionView` documentation is sparse at best. `performBatchUpdates`is a complete enigma. I have been trying to get all 4 (move, add, delete, reload) operations working correctly in the update space. I have some hope that Apple has correctly implemented the handling but I wish they would explain what on earth they are doing.



Here is what I'm trying to get to work. I have two arrays with similar items in various orders and animate the changes. Simple right!? Here are the arrays:



po newItems.map({ $0.representedID })

▿ 3 elements

- 0 : "5ccbdfd08d9b423a9bb78310ffe7e92b"

- 1 : "9130a12b035543099f30a6cd9c6b6081"

- 2 : "e0d1a52ed9f3440b859d47db4f4709f4"



po previous.map({ $0.representedID })

▿ 4 elements

- 0 : "9130a12b035543099f30a6cd9c6b6081"

- 1 : "458b0523c4104a4bbcb420dc01aeb143"

- 2 : "e0d1a52ed9f3440b859d47db4f4709f4"

- 3 : "5ccbdfd08d9b423a9bb78310ffe7e92b"





You can see we lose one item between previous and new items and the rest shift around a bit.





I have some logic to calculate the differences and process that in the perform batch updates which are below:



Changes for section:0

Removing :[1]

Moving from:0 to 1

Moving from:3 to 0

reloading :[2]



This makes sense to make the previous turn into the new we remove 1, move item in 0 to 1, move item in 3 to zero and simply reload index 2.



This crashes every time here:



*** Assertion failure in -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:animator:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3683.300.2/UICollectionView.m:5744

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason:

'attempt to perform an insert and a move to the same index path (<NSIndexPath: 0xc000000000200016> {length = 2, path = 0 - 1})'





Where is this magic insert coming from? Any ideas the algorithm they are using performing these changes?





Here is the sequence of the batch changes:


collectionView.performBatchUpdates({

model.applyChanges(changes)



print("Changes for:\(section)")





if !changes.removeIndexes.isEmpty {

print("Removing :\([Int](changes.removeIndexes))")

self.collectionView.deleteItems(at: changes.removeIndexes.map({ IndexPath(row: $0, section: section)}))

}



for (from, to) in zip(changes.moveFrom, changes.moveTo).sorted(by: { $0.0 < $1.0 }) {

print("Moving from:\(from) to \(to)")

self.collectionView.moveItem(at: IndexPath(row: from, section: section), to: IndexPath(row: to, section: section))

}



if !changes.addIndexes.isEmpty {

print("adding :\([Int](changes.addIndexes))")

self.collectionView.insertItems(at: changes.addIndexes.map({ IndexPath(row: $0, section: section)}))

}



if !changes.reloadIndexes.isEmpty {

print("reloading :\([Int](changes.reloadIndexes))")

self.collectionView.reloadItems(at: changes.reloadIndexes.map({ IndexPath(row: $0, section: section)}))

}



}, completion: { _ in

print("Done updating:\(section)")

self.updateEmptyState()

})

Add a Comment

Replies

Hi utahwithak,

check out 2018s session "A Tour of UICollectionView" (https://developer.apple.com/videos/play/wwdc2018/225/?time=1754). The example around 29:00 is about the same problem you are having here.

  • A big thanks for linking the Video. Now, I have a crystal clear concept, How to performBatchUpdate. 🙏🏼

Add a Comment