TL;DR Applying data source snapshot didn’t reload existing items as expected. Apple’s updated doc is pretty comprehensive on the subject, and covers the issue. Though reload and reconfigure need to be done manually, at least there is now an “official” way to do it.
The issue: Diffable Data Source won’t reload items automatically
For some reason, I thought that new Apple’s diffable data sources are so modern, that they would just work for the whole “update table” thing like a magic one liner, detecting and handling all the possible changes, without exceptions. Doc on NSDiffableDataSourceSnapshot supports that guess by having fields for all the kinds of “change” information: inserts/deletions/moves/reloads.
But for some reason, the reload part didn’t work as I expected.
You can play with the playground a bit to see that if we don’t call reloadItems(_:) manually on the snapshot, there is no way to get “reloadItems” to be printed.
Without explicit call to
reloadItems(_:), the diff system either sees
Item as same item (thus no need to do anything) or as completely different item (thus performing quite obvious insert-after-delete sequence).
The reason: underlying diff algorithm?
Probably, Apple uses CollectionDifference inside their implementation of apply(_:animatingDifferences:) method to determine the diff between two data source snapshots. And as we can see,
CollectionDifference doesn’t have reload info, or any info to derive reloads from. Only insert/remove/move. So even though
NSDiffableDataSourceSnapshot has the slots to put such info into, the implementation of “automatic” apply is just not sufficient enough to provide it.
Solution #1: Apple’s recommended manual way
Apple’s doc on Updating Collection Views Using Diffable Data Sources has the cure. They suggest the app detect’s changes and calls reconfigure on existing snapshot.
Update Existing Items
To handle changes to the properties of an existing item, an app retrieves the current snapshot from the diffable data source and calls either
reloadItems(_:)on the snapshot. Then it applies the snapshot to the diffable data source, which updates the display of the specified items.
Even though it turns out we have to handle the case separately from inserts/removes/moves, it looks pretty simple eventually
// Supposed we are inside code block that handles item change notification ... var snapshot = dataSource.snapshot() snapshot.reconfigureItems([itemId]) dataSource.apply(snapshot, animatingDifferences: true)
They also have pretty funny emphasis there, for the ones still wondering if handling changes to existing objects requires such “completely separate” code route.
Again, the app, not the diffable data source, detects the data changes.
You should also check out the doc if other questions have bothered you as much as me, like:
- Should we put model object or merely it’s identifier as Item in Snapshot
- If we store object or struct, is it ok that the only way data source handle’s changed item’s is performing remove/insert sequence
See Populate Snapshots with Lightweight Data Structures for discussion on both topics
Solution #2: Use another difference algorithm framework
Check out DifferenceKit. One of their selling points, is that the library supports Reload, while
Swift.CollectionDifference doesn’t. The Github page is also a good place to learn more about diffing, if you’re interested.