DiffableDataSources in TableViews and CollectionViews
The new declarative way of setting up UITableView & UICollectionView
At WWDC 2019 a lot of new great UIKit
additions were announced for iOS 13, like compositional layout for collection views or today’s topic diffable data sources for both collection and table views.
They’re a replacement for the good old UICollectionViewDataSource
and UITableViewDataSource
protocols and make it easier to migrate changes in our data views.
Traditional Approach
The traditional approach for plugging data sources requires conforming to the UITableViewDataSource
protocol and implementing the methods numberOfItemsInSection
, numberOfSections
, cellForItemAt
.
This works all fine for simple TableViews
and CollectionViews
until we need to start updating the rows. For updating, both the approaches, reloadData()
and performBatchUpdates()
, had their own sets of issues.
While reloadData()
would ruin our chances of showing nice animations, performBatchUpdates()
and would quickly lead to errors if not handled carefully.
What are Diffable Data Sources?
Diffable data sources mark a shift toward a declarative paradigm by handling mechanisms like synchronization, updating the changes on their own, thereby making things less error-prone and with the new state-driven approach.
By using the diffing tool, diffable data sources take care of updating the table view and collection view rows between the states (current and new).
Like said It replaces well-known methods like cellForRowAtIndexPath:
and numberOfItemsInSection:
.
The new diffable data source abstracts a significant amount of UICollectionViewDataSource / UITableViewDataSource
‘s logic.
Rather than telling the data source how many items to display, we tell it what sections and items to display.
The diffable part of UICollectionViewDiffableDataSource/ UITableViewDiffableDataSource
means that whenever we update the items we’re displaying, the collection view or table view will automatically calculate the difference between the updated and the one previously shown. This will cause the collection or the table view to animate the changes, such as updates, insertions, and deletions.
To do all this the diffable data source requires a snapshot of our model data. This snapshot (NSDiffableDataSourceSnapshot
) contains the sections and items that are used to render our page. Apple refers to these sections and items as identifiers (ItemIdentifierTyp
). The reason for this is that these identifiers must hashable, and the diffable data source uses the hash values for all identifiers to determine what’s changed. After modifying the snapshot, we useapply()
to commit the changes which updates the table view UI.
From now we will focus only on UITableView
for simplicity purposes, but everything that is said for table views will also apply for UICollectionView
likewise.
Set up table view or collection view using Storyboard or Programmatically
We configure the table view in a classical way. The requirements are the same.
Setting Up the Diffable Data Source
UITableViewDiffableDataSource
is a generic class that has two types. Both types need to conform to the Hashable
protocol:
SectionIdentifierType
: representes the sections of the table view or collection view.ItemIdentifierType
: represents the items of a particular section.
Define the Section identifier type
It’s good practice to use an enum
which is by default Hashable
and have your sections as cases of the enum type
Data is provided through snapshots(NSDiffableDataSourceSnapshot
): a snapshot of data. This already describes how diffable data source work. Snapshots of data are compared with each other to determine the minimum amount of changes needed to go from one snapshot to another.
Snapshots don’t rely on index paths for updating items. Instead, there rely on type-safe unique identifiers to identify their sections and items uniquely.
We also use the hash
function to define which property of the Item type should be used for hashing the type's uniqueness. In the case below we use the identifier
property.
Let’s explore the DataSource
class:
The DataSource
class has two generic types, one for the section identifier and one for the item. Both are constrained so that whatever type fills the generic type must conform to Hashable
.
Using the DataSource
subclass we can now update our declaration for the dataSource
instance to use this subclass.
cellProvider
is a closure that has 3 arguments: a pointer to the tableView, indexPath of the current item and the item itself. This closure returns an optionalUITableViewCell
. In many ways, this closure is a replacement for thetableView(_:cellForRowAtIndexPath:)
method.- Here we add the default row animation to the data source. The default animation is
.automatic
. Some other options include.fade
.top
and.bottom
.
3. Assign the dataSource
to the tableView
.
Setting up the snapshot
To update or populate the data source, we simply add the necessary sections and its items on a snapshot instance and apply, update or remove data on it.
We used to do that by implementing methods from the UITableViewDataSource
but with new diffable data source we need to do that a little bit differently - we need to create a snapshot (NSDiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType>
) which is also a generic class, similar to the UITableViewDiffableDataSource
.
The basic steps for setting up a snapshot are as follows:
- Declare an instance of
NSDiffableDataSourceSnapshot
which needs to match the section and item type you specified for the data source earlier. - Use the instance methods
shuffled()
on the elements
3. Append the required sections to the snapshot.
4. Append the items for the section or each section if the table view or collection view has multiple sections.
5. Apply the snapshot to the data source. This step is the required step to update the current snapshot which will render items to the table view or collection view.
Just need remember that types in both snapshot and data source must be the same.
Update or remove data
To update or remove data from the table view, we need to get a hold of the current snapshot which is done by invoking dataSource.snapshot()
.
To access items that have index path based APIs in diffable data sources, we need to translate the index path to the item identifier in the following way:
dataSource.itemIdentifier(for: indexPath)
The following code snippet removes elements when selected from the UITableView
:
With the following line we are getting the current snapshot:
let currentSnapshot = dataSource.snapshot()
A Little reminder:
- UITableViewDiffableDataSource
- UICollectionViewDiffableDataSource
- NSDiffableDataSourceSnapshot
Snapshot:
appendSections(_:)
- add sections to the snapshotappendItems(_:)
- add items to the current sectionappendItems(_:, toSection:_)
- append items to a given sectionsectionIdentifiers
- get back all the sectionsdeleteItems(_:)
- remove items from the snapshotindexOfItem
- return the index of an itemsectionIdentifier(containingItem: _)
- get the section for a given iteminsertItems(_:, afterItem: _)
- insert a given source item(s) after a destination iteminsertItems(_:, beforeItem: _)
- insert a given source item(s) before a destination item
Data source:
snapshot()
apply(_, animatingDifferences:)
itemIdentifier(for:)
- uses the index path to retrieve the current item- CellProvider — clousure argument on the data source initializer which has 3 arguments: a pointer to table view or collection view, the current index path and the current item
Conclusion
We’ve seen how the diffable data source introduces a whole new way of building data sources for our collection view and table view. Working with collection or table views data that changes over time is much more appealing thanks to the diffable data sources (as well as compositional layouts) and the new way of declarative programming .
If you want to get in touch and by the way, you know a good joke you can connect with me on Twitter or Linkedin.
That’s it for this one. I hope you enjoyed reading it.😄 🙌