25 Mar 20205 min read

Diffable Data Sources

It has been quite some time since WWDC 2019 when a lot of new great UIKit additions were announced, like Compositional Layout for collection views or today's topic - diffable data sources for both collection and table views. As most projects I have been working at so far supports iOS 12 and older I didn't have a chance to get my hands on those new great features. Do not worry if you are in the same situation as me, iOS is coming "soon" and we might increase the minimum iOS version to iOS 13 for some projects at least. Anyway, thankfully I had a chance to work on some side project recently and I could pick whatever minimum version I wanted so finally I could try it out... and I must say it's great!

You might have used methods like insert, delete, reload, or move on table or collection views together with performBatchUpdates to update it with animation or to just reload cells that changed and not all of them as it would be with reloadData. And if you didn't have a chance to use this "old" method, you must trust me it was a pain to handle this properly in some cases.

So thank you Apple for finally bringing some better way to achieve this, better late than never 👍

Implementation

The new data source class UICollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> is a generic class where both SectionIdentifierType and ItemIdentifierType have to be Hashable. That means you can use ie. as simple type as an Int for your section identifier, an enum to make it more descriptive or any other object that conforms to this protocol.

>The table view equivalents are pretty similar in the implementation so I am pretty sure once you understand it, you can easily implement it on your own.

So let's create a simple grid with various background colors that shuffle every few seconds as presented below: Final result


If you want to see just a whole code, you can find it here: Playground Gist


To do that, we need to create the usual UICollectionView (do not forget to register your cell 😅) with whatever layout you want. I have used the Flow layout for this. The more interesting part is a new data source

let dataSource = UICollectionViewDiffableDataSource<Int, UIColor>(collectionView: collectionView) { collectionView, indexPath, item in
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "sampleIdentifier", for: indexPath)
    cell.backgroundColor = item
    return cell
}

which we assign to our collection view

collectionView.dataSource = dataSource

Now, we need to fill it with the actual data. We used to do that by implementing methods from the UICollectionViewDataSource but with new diffable data sources 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 UICollectionViewDiffableDataSource

var snapshot = NSDiffableDataSourceSnapshot<Int, UIColor>()
snapshot.appendSections([0])
snapshot.appendItems(items, toSection: 0)

I have used simply an integer for identifying my sections, but you could as well use ie. enum like this (just remember that types in both Snapshot and DataSource must be the same):

enum Section: CaseIterable {
    case firstSection
    case secondSection
}
...
var snapshot = NSDiffableDataSourceSnapshot<Section, UIColor>()
snapshot.appendSections(.firstSection)
snapshot.appendSections(.secondSection)
...

So there is plenty of ways to implement it and make your code more readable.

The only thing left to do is to apply our changes to the snapshot

...
let items = colors.shuffled()
snapshot.appendItems(items, toSection: 0)
dataSource.apply(snapshot, animatingDifferences: true)

where as items I have used an array with colors:

let colors: [UIColor] = [
        .brown,
        .purple,
        .systemBlue,
        .systemRed,
        .systemGray,
        .systemPink
    ]

There are also other operations available that you can do on a snapshot, like deleting, moving, reloading, inserting items before or after certain items, etc.

ADVERTISEMENT
Sponsor Ordinary Coding
Sponsor Ordinary Coding

If you are interested in advertising your application or service on my blog, get in touch with me by email ordinary.coding@protonmail.com. If you are an indie developer you can have your app advertised completely for free!

Supplementary Views

What about adding a section header or footer? To configure headers, we need to set supplementaryViewProvider property on our data source

dataSource.supplementaryViewProvider = { collectionView, kind, indexPath -> UICollectionReusableView? in
    switch kind {
    case UICollectionView.elementKindSectionHeader:
        guard let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "sampleHeaderIdentifier", for: indexPath) as? SampleCollectionReusableView else {
            fatalError("Header is not registered")
        }


        headerView.fill(with: "My Awesome Colours")
        return headerView
    default:
        fatalError("Element \(kind) not supported")
    }
}

It all might feel a little bit messy... I know, but if you can afford supporting iOS 13 or above and need to change data in collections with animation, etc, that's the easiest and recommended way to achieve this.

Conclusion

Seems like Apple did not forget about iOS and macOS developers and still bring us some new additions to UIKit as well and not just SwiftUI as I am pretty sure a lot of us won't ditch UIKit anytime soon. I wish there was a better way to distribute it by making it accessible also on older iOS versions but well, we cannot have everything.

Anyway, 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). The only thing that I do not like is the inconsistency between delegates and data sources. Those feel like from different worlds and because of that, the code might be less readable and consistent as we use a different pattern for each of them.

Also, it's worth mentioning that AppKit and macOS's equivalents of table and collections views (NSCollectionViewDiffableDataSource, NSTableViewDiffableDataSource) also got the Diffable Data Sources, so it's a really big plus for Apple 🔥.

That's all for this article and I hope that you will find it useful.

Thank you for reading! Follow me on Twitter and in case of any questions, do not hesitate to contact me.