`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()
})