How to refresh header / footer with Diffable Data Source

All that is needed is correctly configured section with associated value. Learn how to properly refresh supplementary views with Diffable Data Source.

Published: Dec. 20, 2020
See books

Refreshing header/footer views in either TableView or CollectionView used to be pretty fickle business. There are multiple options (including the nuclear one of reloadData()) but none seemingly made for this task.

This thankfully changes with Diffable Data Sources. Still there is not a new method called reloadHeader(at indexPath: ) but once you understand how headers and footers work with Diffable then refresh is a breeze.

Refreshing explained

If we want Diffable to refresh our header (or footer, but I will use just a header as an example), we need to tell it that content changed. Headers are connected to the sections and if the section changes, then Diffable Data Source will automatically reload supplementary views (meaning headers and footers) for that particular section.

Let's look at an example. We want a section with dynamic header that will change based on some state. We can define this simple enum:

enum Section: Hashable {
    case main
}

Which will work, but from the Hashable standpoint, there is only one section that is always identical. However we can use associated value (which must be also Hashable) like this:

enum Section: Hashable {
    case main(title: String)
}

And now, there is basically infinite number of main section variants that can exist.

We can now construct snapshot and set the title we want as the associated value. Once this title changes, applying snapshot will automatically reload our header. This concept may not be immediately obvious, but once you use it, it becomes quite clear.

If you have more complex header and want it to update each time you apply new snapshot, you can use UUID as an associated value and simply create new instance each time 🙂

Associated value can be anything you want (as long as it conforms to Hashable) which is great, because you can save all the data you need to configure the header there and when you dequeue it from the CollectionView, you don't have to reach elsewhere to get the data.

Example

To better explain this, here is an example from my open source project:

enum Section: Hashable {
    case favoriteJokes(count: Int)
    case jokes
}

Sections contain either favorited jokes (with associated count) or random fetched from an API.

And here is the method to configure the header:

func supplementary(collectionView: UICollectionView, kind: String, indexPath: IndexPath) -> UICollectionReusableView? {
    // dequeue removed for brevity

    let section = datasource.snapshot().sectionIdentifiers[indexPath.section]

    switch section {
    case .favoriteJokes(let count):
        header.configure(with: "Favorites (\(count))")
    case .jokes:
        header.configure(with: "Random")
    }

    return header
}

Thanks for reading!

Uses: Xcode 12 & Swift 5.3

Bluesky logo

Follow on Bluesky to not miss new posts

Filip Němeček profile photo

WRITTEN BY

Filip Němeček Mastodon

iOS blogger and developer with interest in Python/Django.

iOS blogger and developer with interest in Python/Django.