Diffable Data Sources Adoption with Ease

Diffable Data Sources were introduced at WWDC 2019 and are available since iOS 13. They’re a replacement of the good old UICollectionViewDataSource and UITableViewDataSource protocols and make it easier to migrate changes in your data views.

Diffable Data Sources come with a few benefits over using the classic data source approach and are the preferred way nowadays to work with collection- and table views. However, it might not always be easy to rewrite existing code which is heavily integrated into using the old approach. This is too bad as it takes away the opportunity to benefit from this great new API.

Therefore, I’ve decided to introduce you to a simple extension on diffable data sources which allows you to integrate this in existing projects without too much hassle. But before we dive in, let’s first take a look at what diffable data sources are and what kind of benefits they bring.

What are Diffable Data Sources?

A diffable data source provides the behavior you need to manage updates to your table- or collection view’s data and UI in a simple, efficient way. It replaces well-known methods like cellForRowAtIndexPath: and numberOfItemsInSection:.

There are two classes: UICollectionViewDiffableDataSource and UITableViewDiffableDataSource for either collection- or table views. An example of construction could look as follows:

struct BlogPost: Hashable {
    let title: String
}

let collectionView = UICollectionView()
let dataSource = UICollectionViewDiffableDataSource<Int, BlogPost>(collectionView: collectionView) { (collectionView, indexPath, blogPost) -> UICollectionViewCell? in
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BlogPostCell", for: indexPath) as! BlogPostCell
    cell.titleLabel.text = blogPost.title
    return cell
}

The data source is generic and defines a type for section identifiers and a type for the data that is listed. The cell provider will take this generic as output for the third argument containing the data for which a cell needs to be returned.

Data is provided through so-called snapshots: a snapshot of data. This already describes how diffable data sources work. Snapshots of data are compared with each other to determine the minimum amount of changes needed to go from one snapshot to another.

An example of providing a snapshot of data can look as follows:

var snapshot = NSDiffableDataSourceSnapshot<Int, BlogPost>()
snapshot.appendSections([1, 2])

snapshot.appendItems(mostPopularBlogPosts, toSection: 1)
snapshot.appendItems(recentBlogPosts, toSection: 2)

dataSource.apply(snapshot, animatingDifferences: true)

The data source is smart enough to calculate the differences and replaces code like performBatchUpdates, for example.

Diffable Data Sources result in code that is more compact and centralized while at the same time bringing several benefits. In the end, the system takes over a lot of the heavy lifting. To make you understand what this all includes it’s good to list some of the benefits.

Benefits of using Diffable Data Sources

If you’ve implemented collection- or table view data sources without diffable data sources you’ve probably run into the following error:

Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (1) must be equal to the number of items contained in that section before the update (1), plus or minus the number of items inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).

It’s a result of managing data ourselves in which an update to the collection view is not in sync with the locally managed data. It’s often a result of using performBatchUpdates or migrating data returned from an NSFetchedResultsControllerDelegate.

Diffable Data Sources aims to take away this error completely by taking over the responsibility to calculate changes between snapshots.

Adoption of diffable data sources in an NSFetchedResultsController

Another place in which diffable data sources really shine is the delegate of an NSFetchedResultsController. When displaying a list of Core Data Entity we’re often running into updates after an entity changed. We would iterate over each NSFetchedResultsChangeType to make sure we perform all those changes using the correct collection- or table view method.

Each update is executed inside a performBatchUpdates and we cross our fingers to hope for the best result possible.

This is another point in which the earlier mentioned error could occur. It’s also a lot of extra code that we have to write for applying changes. Luckily enough, we can now adopt one of the methods that replace all this logic inside the NSFetchedResultsControllerDelegate:

@available(iOS 13.0, *)
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
    guard let dataSource = collectionView?.dataSource as? UICollectionViewDiffableDataSourceReference else {
        fatalError("The data source has not implemented snapshot support while it should")
    }
    dataSource.applySnapshot(snapshot, animatingDifferences: true)
}

That’s all the required code to apply changes using snapshots in Core Data.

An extension to migrate existing code without too much hassle

But how about migrating old code? I promised you a simple extension that simplifies the migration to diffable data sources. We’ve implemented this in the Collect by WeTransfer app in less than a day and we already benefit from this new API.

The code reuses the existing data source methods to provide cell configurations. It’s still required to provide snapshots of your data but this should be relatively easy and differs case by case.

The generic code looks as follows:

@available(iOS 13.0, *)
extension UICollectionViewDiffableDataSourceReference {

    /// A convenience initialiser to set up a `UICollectionViewDiffableDataSourceReference` with existing data source methods. This makes it low effort to use diffable datasources while keeping existing code untouched.
    /// Eventually, when dropping iOS 12, you might want to revisit this code and remove the existing `UICollectionViewDataSource` implementations.
    ///
    /// - Parameters:
    ///   - collectionView: The collection view for which we're setting the data source
    ///   - collectionViewDataSource: The original existing collection view data source.
    convenience init(collectionView: UICollectionView, collectionViewDataSource: UICollectionViewDataSource) {
        self.init(collectionView: collectionView) { [weak collectionViewDataSource] (collectionView, indexPath, _) -> UICollectionViewCell? in
            return collectionViewDataSource?.collectionView(collectionView, cellForItemAt: indexPath)
        }

        supplementaryViewProvider = { [weak collectionViewDataSource] (collectionView, kind, indexPath) -> UICollectionReusableView? in
            return collectionViewDataSource?.collectionView?(collectionView, viewForSupplementaryElementOfKind: kind, at: indexPath)
        }
    }
}

As you can see, the code requires you to pass in two parameters:

  • The collection view we’re updating
  • A reference to the original collection view data source

Inside the cell- and supplementary view provider we’re using the original data source to fetch the views. This simplifies the implementation of diffable data sources in existing projects by a lot, especially if you can make use of the earlier mentioned NSFetchedResultsControllerDelegate method.

Do make sure to revisit your code for a clean up of old data source methods once you’re able to drop iOS 12. In the end, it’s recommended to completely step away from UICollectionViewDataSource implementations to make your code readable and understandable. The same code applies for UITableViewDiffableDataSource with a few simple code changes:

@available(iOS 13.0, *)
extension UITableViewDiffableDataSourceReference {

    convenience init(tableView: UITableView, tableViewDataSource: UITableViewDataSource) {
        self.init(tableView: tableView) { [weak tableViewDataSource] (tableView, indexPath, _) -> UITableViewCell? in
            return tableViewDataSource?.tableView(tableView, cellForRowAt: indexPath)
        }
    }
}

A note about UICollectionViewDiffableDataSourceReference

I want to point out that we’ve used an extension on top of UICollectionViewDiffableDataSourceReference. This allows us to step away from the generics and make our code available to any data source of any type. This is not the recommended way as Apple states:

If you’re working in a Swift codebase, always use UICollectionViewDiffableDataSource instead.

However, the code is available and works perfectly. It’s the Swift representation of the Objective-C object you use to manage data and provide items for a collection view and it’s, therefore, unlikely to be unmaintained. If you see an opportunity in your code to make use of the generics instead, I recommend doing so.

Conclusion

Diffable Data Sources are a great new addition to UIKit for handling changes in collection- and table views. It takes away an annoying error we’ve all seen several times and simplifies the way we build our data representations.

If you like to improve your Swift knowledge, even more, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.

Thanks!