Modern table views with diffable data sources
Tags: ios, swift
Published: 2019-12-24 10:43:57

At WWDC 2019 Apple announced a couple of really cool features for table views and collection views. One of these cool features comes in the form of `UITableViewDiffableDataSource` and its counterpart `UICollectionViewDiffableDataSource`. These new diffable data source classes allow us to define data sources for collection- and table views in terms of snapshots that represent the current state of the underlying models. The diffable data source will then compare the new snapshot to the old snapshot and it will automatically apply any insertions, deletions, and reordering of its contents.

In today's article, I will show you how to use `UITableViewDiffableDataSource` to drive your table views. Since the table view data source is pretty much the same as the collection view version apart from some class names, I will focus only on the table view variant. The following topics are covered in this article:

* Understanding how a diffable data source is defined.
* Using a diffable data source in your apps.
* Some best-practices to consider when using a diffable data source.

By the end of this article, you will know exactly how to use diffable data sources and what their caveats are.

## Understanding how a diffable data source is defined

A diffable data source is an object that replaces your table view's current `UITableViewDataSource` object. This means that it will supply your table view with the number of sections and items it needs to render, and it supplies your table view with the cells it needs to display. To do all this the diffable data source requires a snapshot of your model data. This snapshot contains the sections and items that are used to render your page. Apple refers to these sections and items as identifiers. 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. Let's look at this a little bit more in-depth by exploring the type signatures of both the data source and the snapshot.

First, let's explore the `UITableViewDataSource` signature:

```swift
class UITableViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> : NSObject 
  where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable
```

That's quite the mouthful! The `UITableViewDiffableDataSource` 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`. If you're not familiar with generics, check out [this post](https://www.donnywals.com/an-introduction-to-generics-in-swift-using-its-built-in-types/) I wrote as an introduction to generics in Swift.

It's interesting that Apple has decided to call the data source's generic parameters `SectionIdentifierType` and `ItemIdentifierType`. If your data model conforms to hashable, you can use it as the `SectionIdentifierType` or as the `ItemIdentifierType`. But, as the name of the generic suggests that might not be the greatest idea. I will explain why in the best practices section. For now, what matters is that you understand that both identifiers must conform to `Hashable` and that the data source will use hash values to determine changes in your data set.

Now let's look at the second key player in using a diffable data source; the snapshot:

```swift
struct NSDiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType> 
  where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable
```

The snapshot object is a struct rather than a class, and it has the same generic parameters as the diffable data source it's applied to. This means that you can't apply a snapshot with a set of identifiers to a data source with different identifiers and your code will fail to compile.

Now that you know how a diffable data source is defined, and have a rough idea of how it works, let's get more practical and see how you can use a diffable data source in your apps.

## Using a diffable data source in your apps

In this section, I will use a very simple data model where my section identifiers are integers, and my model contains only a title property. In reality, your models will be much complicated than what I'm using here. However, part of the beauty of diffable data sources is that this doesn't matter. Whether your model is simple or more complex, the principles all remain the same.

### Setting up the basics

To create a new diffable data source, you create an instance of `UITableViewDiffableDataSource` as follows:

```swift
let datasource = UITableViewDiffableDataSource<Int, MyModel>(tableView: tableView) { tableView, indexPath, itemIdentifier in
  let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath)
  
  // configure cell
  
  return cell
}
```

In the preceding example, we use `Int` and `MyModel` as the identifiers for the sections and items respectively. We pass the table view that the diffable data source instance should be applied to the initializer of `UITableViewDiffableDataSource` and we also pass it a closure.

This closure is the cell provider. It's called whenever the table view needs a new cell to render on screen. In many ways, this closure is a replacement for the `tableView(_:cellForRowAtIndexPath:)` method you might be used to implementing currently. The main difference is that the item identifier that corresponds to the index path for the table view is passed along to this closure. So in the preceding example, you would receive an instance of `MyModel` as the third parameter for the cell provider closure. This means that if your identifier type contains all properties that are needed to render your cell, you don't have to fetch this object from your underlying data storage anymore.

Let's create a new snapshot that can be applied to our diffable data source:

```swift
var snapshot = NSDiffableDataSourceSnapshot<Int, MyModel>()
snapshot.appendSections(storage.sections)

for section in storage.sections {
  snapshot.appendItems(storage.modelsForSection(section), toSection: section)
}

datasource.apply(snapshot)
```

> **Note:**  
> The preceding code uses a `storage` object. It's not a built-in type or anything but rather a placeholder that I made up to use in this example. I'm sure you can derive what this storage object's `section` property and the `modelsForSection(_:)` method look like. The main point is that you understand how the snapshot is configured.

The snapshot is created as a `var` because it's a struct and we wouldn't be able to modify it if we had declared it as a `let`. First, we call `appendSections(_:)` on the snapshot. This method takes all section identifiers you want to have present in your table view. Then, we loop through all sections and call `appendItems(_:toSection:)` on the snapshot. This will associate an array of item identifiers with the section identifier that's passed to this method.

Once all items and sections are added to snapshot, it is passed to the data source by calling `apply`. 

Note that your data source will not update the table view unless you explicitly create a snapshot and call `apply` to update the table view. If you have already applied a snapshot and want to perform a simple add or remove operation of an item or section, you can get the data source's existing snapshot, modify it, and apply it as follows:

```swift
var currentSnapshot = datasource.snapshot()
currentSnapshot.deleteItems([itemToDelete])
datasource.apply(currentSnapshot)
```

The preceding code takes a snapshot of the data source, deletes an item from it and then applies the snapshot to the data source. `NSDiffableDataSourceSnapshot` has methods to delete items, sections and even to wipe the entire snapshot clean. If you want to update your data source, it's up to you to decide whether you want to create a new data source from scratch or to update the current snapshot.

With a setup like this, you can already provide a table view with data. Let's see how you can add support for section headers and other table view features you might have implemented in the past. For example, cell deletion.

### Adding section headers and interactions

Out of the box, the diffable data source is pretty plain. But since it conforms to `UITableViewDataSource` it can do anything you might be already doing in your current `UITableViewDataSource` implementation. All you need to do is define your own `UITableViewDiffableDataSource` and override the methods of features you want to implement. For example, to add section headers you can override `tableView(_:titleForHeaderInSection:)`:

```swift
class MyDataSource: UITableViewDiffableDataSource<Int, MyModel> {
  override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    return "This is section: \(section)"
  }
}
```

When you subclass `UITableViewDiffableDataSource`, you immediately fill in the generic parameters for the section- and item identifiers. In this case, `Int` and `MyModel`.  Note that the `section` argument in `tableView(_:titleForHeaderInSection:)` is not a `SectionIdentifierType`, instead it's always the integer index of your section. Keep this in mind when you construct your section title. If you would run your app and use this data source subclass instead of the regular `UITableViewDiffableDataSource` class, you will find that you now have support for section headers.

If you want to add support for deleting items you need to override `tableView(_:canEditRowAt:)` and `tableView(_:commit:forRowAt:)` in your data source subclass:

```swift
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
  return true
}

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
  if editingStyle == .delete {
    let model = storage.modelForIndexPath(indexPath)

    var snapshot = self.snapshot()
    snapshot.deleteItems([model])
    apply(snapshot)

    storage.deleteModel(model)
  }
}
```

Notice how the preceding code finds the model that needs to be deleted in the underlying storage and then updates the existing data source on the snapshot. Finally, the model is also deleted from the underlying storage to ensure that the deletion is persisted.

## Best practices for using diffable data sources

During my time exploring diffable data sources, I have run into a [problem](https://twitter.com/DonnyWals/status/1205861706179723264) where I couldn't get cells to reload themselves. Luckily [Steve Breen](https://twitter.com/__breeno) was kind enough to respond to my call for help and I learned a couple of things from him. But even then I was having trouble. Eventually [Chris Hinkle](https://twitter.com/lilhinx) came through with an [observation](https://twitter.com/lilhinx/status/1205904266847637504). Based on what I've learned I have found two best practices that I think are important to keep in mind.

### Keep your identifiers simple and to the point

When you choose your identifiers for your diffable data source and snapshot, try to make sure they only include data that will be rendered or influences the rendering. When I was stuck with reloading my cells, I was using `NSManaged` object subclasses to drive my data source. This seems to work well enough because everything was there. Items were added, removed and reordered. However, for some reason, my data source never seemed to pick up changes to the properties of my managed objects. I eventually got around this by providing a struct that contained the data I wanted to render rather than the entire managed object. A nice way I've found to define these structs is as extensions on the models themselves:

```swift
extension MyModel {
  struct Diffable {
    let id: UUID

    let title: String
    let subtitle: String
    // other properties that will be rendered on the cell

    init(model: MyModel) {
      self.id = model.id
      self.title = model.title
      self.subtitle = model.subtitle
    }
  }
}
```

When using this approach, you'd replace the `MyModel` item identifier with `MyModel.Diffable`, and when you create your snapshot, you must convert all `MyModel` instances to `MyModel.Diffable`. Since the initializer for the `MyModel.Diffable` object takes an instance of `MyModel` this is fairly straightforward:

```swift
var snapshot = NSDiffableDataSourceSnapshot<Int, MyModel>()
snapshot.appendSections(storage.sections)

for section in storage.sections {
  let items = storage.modelsForSection(section).map(MyModel.Diffable.init)
  snapshot.appendItems(items, toSection: section)
}

datasource.apply(snapshot)
```

By mapping over the array of `MyModel` objects using the `MyModel.Diffable.init`, every model object will be passed to the `MyModel.Diffable` initializer and is converted to an instance `MyModel.Diffable`. Pretty nifty, right?

Notice that there is one property in my `Diffable` extension that shouldn't be rendered; the `id` property. I'll explain why in the next subsection.

### Ensure that your identifiers can be uniquely identified

If you don't include any way of uniquely identifying your identifiers, it's really hard for the diffable data source to find out what changed. For example, look at the following code:

```swift
let itemsBefore = [{ "name": "Donny Wals" }, { "name": "Donny Wals" }]
let itemsAfter = [{ "name": "Donny Wals" }, { "name": "Donny Wals" }]
```

The preceding snippet shows two arrays that appear to be identical. And while they might _look_ identical, each object is its own entity. And they might have some kind of underlying ordering and the objects might have swapped positions. It's impossible to tell, right?

The same is true for your diffable data source. If you don't provide it anything unique to identify objects by, it can get confused easily which is probably not what you want. If you include some kind of known unique identifier like a `UUID` or an identifier that's used in your back-end, it's much easier to keep track of changes to ordering and individual changes. If you're reading data from a server that you won't modify and you know it already has a sense f uniqueness, for example if every item points to a unique resource on the web, it might be redundant to add an extra identifier yourself.

## In summary

I hope I have been able to not only introduce you to diffable data sources, but I hope I have also been able to teach you how you can use them in the real world. Several blog posts I have seen were written shortly after WWDC 2019 and cover the very basics, and unfortunately Apple's documentation this year hasn't been fantastic. I'm sure they will get around to updating it eventually. Luckily, Apple did include some sample code in the links for their [Advances in UI Data Sources](https://developer.apple.com/videos/play/wwdc2019/220/) talk from WWDC 2019.

In today's post, I have shown you how a diffable data source is defined, and how it's used. You saw that a diffable data source has two generic parameters; one for sections and one for items, and you saw that a diffable data source requires a snapshot of your models to render data. I have also explained how you can subclass `UITableViewDiffableDataSource` to add support for features like section headers and swipe-to-delete. Lastly, I shared some best practices that you should keep in mind when using diffable data sources.

If there's anything unclear after reading this post, or if you have feedback for me, make sure to reach out on [Twitter](https://twitter.com/donnywals). I love hearing from you.