Reloading existing items when using Diffable Data Source

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 reconfigureItems(_:) or 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()
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.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: