UITableViewDiffableDataSource
By: Roberto Sampaio
Tags: iOS App Development, Swift Programming, Ios Development, iOS, Swift

Probably everyone that used UITableView before iOS 12 crashed their apps at least once. It's very easy to delete or add an element and forget to sync the tableView and it's dataSource .

What was the problem?

UITableView has a property called dataSource . It's a type that conforms to UITableViewDataSource protocol. It's responsibility is provide to tableView some infos like the number of sections, number of rows per section and which cell will be used by tableView on a specific indexPath . Doesn't matter where the dataSource gets these infos, it's responsible to provide them to tableView . The mandatory methods of UITableViewDataSource are:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Intfunc tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

When we update the tableView removing or updating a row, for example, we need to make sure that dataSource is in sync with tableView , removing this element as well. Otherwise, we're out of sync, and it can leads to a crash.

tableView.deleteRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)

//If the dataSource doesn't update it's data, we'll have a crash later :(

The crash message is similar to the message bellow. In my case I had a section and three rows. I removed the first row without updating my array of elements, where my dataSource was getting the data.

Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (3) must be equal to the number of rows contained in that section before the update (3), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).’

The solution

On WWDC 2019, Apple presented a safer and simpler new API, for UITableView and UICollectionView . With this API, we can update and animate our tableView in a simpler way and with less chances of crashing our app. We're using UITableView , so let's use UITableViewDiffableDataSource .

Instead of creating a project using UITableViewDiffableDataSource , I though that would be better for understanding if I created a project using UITableViewDataSource , and switch later to use the new API.

If you want to read about this project that I created, click here.

If you prefer skip this part and code with me right away, you can download the project in the link bellow.

UITableViewDiffableDataSource

First, we need to change the dataSource .

Open the DiaryViewController file and delete the dataSource assignment…

tableView.dataSource = self

… and the implementation of the protocol.

//Delete the extension and it's content

extension DiaryViewController: UITableViewDataSource {

}

Let's check some methods from dataSource:

open class UITableViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> : NSObject, UITableViewDataSource where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable public typealias CellProvider = (UITableView, IndexPath, ItemIdentifierType) -> UITableViewCell? public init(tableView: UITableView, cellProvider: @escaping UITableViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType>.CellProvider)

It's a generic type. SectionIdentifierType and ItemIdentifierType should be Hashable , because sections and elements must be unique.

It's init gets a tableView and a cellProvider as parameters. The cellProvider returns a cell (like cellForRow , right?).

Now let's implement the dataSource !

NSDiffableDataSourceSnapshot

If you try to run the project, you'll notice it's still not working. tableView is still empty, right? That's because dataSource updates the tableView through NSDiffableDataSourceSnapshot .

All we need to do is just apply a snapshot on dataSource . Then the dataSource verifies the last snapshot state and make a diff with this new snapshot . With this diff, it's able to properly updates the tableView .

Easy, isn't it?

As we don't have a initial snapshot , tableView keeps empty. In order to create a snapshot , we need the following steps in our project:

Init NsDiffableDataSource . It's a generic type, like the UITableViewDiffableDataSource . Add the main section and the notes in it. Apply the snapshot on dataSource .

If you're asking yourself why we passed animatedDifferences as false , you're really a focused reader! This parameter as false means that we don't want the tableView getting animated in this snapshot .

In fact, we don't want to animate at the first iteration. We want the first elements already in the tableView , not seeing them getting added on it.

Done! Now can run the app, and everything is working again, right?

No, it's not :(

Something is wrong. When tapping on edit , we can't delete or move our cells.

And worst. If we try to create a new note, the app will crash.

In part 2 of this study, I teach how to fix that!

Check the part 2 here!

Start project

Final project

See you there!