UITableView Diffable DataSource Part 1

Ali Akhtar
14 min readApr 2, 2022

--

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. I will suggest you read https://medium.com/@ali-akhtar/uicollection-compositional-layout-part-4-datasource-18cfe9f46b15 this blog until Getting Started section where I explained in detail about diffable datasource. This blog will try to cover use cases and special cases for using diffable datasource in actual project

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:

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.

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).’

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:

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

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

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.

Getting Started

As shown in Figure 1 we rendered UITableView by using old style UITableViewDataSource and it is working fine, Now next step to replace it with UITableViewDiffableDataSource

Figure 1

As shown in Figure 2 and 3, we replaced UITableViewDataSource with UITableViewDiffableDataSource. 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. 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.Few things we did in below code

  1. Init NsDiffableDataSource. It's a generic type, like the UITableViewDiffableDataSource.
  2. Add the main section and the data in it.
  3. Apply the snapshot on dataSource.
  4. animatedDifferences as false, 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.
Figure 2
Figure 3

This is our application where we are showing list of payment breakdown typically for ride hailing application

Figure 4

As shown in Figure 5 , after I removed Hashable conformance to PaymentDetailsModel I am getting error,

Figure 5

In Figure Figure 6 we added paymentDetail5 with same value as paymentDetail4 and application got crash Fatal: supplied item identifiers are not unique. because current logic to create hash Value using data in each property , and diffable data source must have unique hash value in snapshot , if snapshot have same hasvalue object it will crash so , always be sure about that

Figure 6

As you can see in Figure 7

  1. For 1 section example we only conform to Hashable which means all value in the object should be same to generate same hash value and same for == operator
  2. For 2 section example we conform to Hashable and say has should be generate with serialNumber which means if onlyserialNumber value in object should be same to generate same hash value and for == operator still whole object value will same
  3. For last section if serialNumber in objects is same it will produce same hash and both object are equal as well
Figure 7

As shown in Figure 9 , after we add id and make it unique , now the issue disappear, In the figure we say element in the model have unique hash if their id is unique also two PaymentDetailsModel is equal if both have same id

Figure 8

In figure 9 , we change the definition of Equtable , now paymentDetail4 and paymentDetail5 have unique hash but they are same / equal (beaucse title is equal). which is also not recommended and it leads to crash “Fatal: supplied item identifiers are not unique. Duplicate identifiers: {(\n TableViewDiffableDataSources.PaymentDetailsModel(id: \”5\”, title: \”Total\”, price: \”40.00\”, currency: \”$\”)\n)}” 0x00006000018c04e0". In short SectionIdentifier and ItemIdentifier in same snapshot should have hash and == unique, The question now arises how diffable data source uses hashValue and == in it’s algo we will see this later.

Rule of Thumb 1 : Snapshot data should have object that are unique hashValue and all object with earch other follow!= to be true

Figure 9

Now we need to create some complex UI through diffable datasource. As you can see here three sections, one is showing Payment Breakdown , second is showing list of payment option you can select , and final current payment method that is selected. I would not go in much detail how I used Autolayout and build these cells and UI stuff. One thing I will like you to focus is the model we create to populate data. It conform to Hashable

Figure 10

As you can see in Figure 11 we created Section and Row enum , Row tell how many type of cell we have with Hashable model as it’s associated type which should unique and when creating UITableViewDiffableDataSource, using item enum we are providing respective UI’s

UITableViewDiffableDataSource

The object you use to manage data and provide cells for a table view.

Declaration

@MainActor class UITableViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> : NSObject where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable

Discussion

A diffable data source object is a specialized type of data source that works together with your table view object. It provides the behavior you need to manage updates to your table view’s data and UI in a simple, efficient way. It also conforms to the UITableViewDataSource protocol and provides implementations for all of the protocol’s methods.

To fill a table view with data:

  1. Connect a diffable data source to your table view.
  2. Implement a cell provider to configure your table view’s cells.
  3. Generate the current state of the data.
  4. Display the data in the UI.

To connect a diffable data source to a table view, you create the diffable data source using its init(tableView:cellProvider:) initializer, passing in the table view you want to associate with that data source. You also pass in a cell provider, where you configure each of your cells to determine how to display your data in the UI.

Figure 11

We create snapshot and apply initial snapshot without animation. Note appendSections you should add sections first then add items to these sections. At this point everything working fine , we rendered UI using diffable datasource having complex multiple sections UI. In the next section we will do some experiment to understand diffable datasource more and will try to cover most of the scenarios you will face while building real application

Figure 12

Insert Section with animation

As shown in Gif 1 Initially snapshot having two section payment breakdown and payment selected. when user tap on Button Payment options section is inserted with left animation as easy . Previously you usually call performBatchUpdates that leads to sometimes crash when data is not sync with UI data. Now this is maintain by iOS itself which is really cool

Gif1

As shown in Figure 13 let’s say when we land into screen we hit API that return data of two sections, we rendered two sections without animation, Now after we rendered we hit another endpoint that gives data os another section. what we do we insert that section later with animation so user can feel some data just came . as you can see we first get the data of old snapshot and then insert section and append items then everything work as in demo

Figure 13

If you not sure , which section is inserting or UI is totally driven from backend , you can create fresh snapshot as well and send it to the apply method it will automatically find diff what changed and do it’s magic, that’s why it is called diffable data source 💃

defaultRowAnimation → The default type of animation to use when inserting or deleting rows.The default value of this property is UITableView.RowAnimation.automatic. If you set the value of this property, the new value becomes the default row animation for the next update that uses apply(_:animatingDifferences:completion:).

Figure 14

As shown in Gif2 we first insert coupon section with left animation and apply snapshot and on its completion we insert paymentType section with right animation

Gif2

As shown in Gif3 we parallel insert coupon section with left animation and apply snapshot and we insert paymentType section with right animation parallel, Just see you don’t care about your datasource update with Ui update evrything is done by iOS , previously rembered performBatchUpdate if you don;t do this thing carefully you faced crashes Inconsistency crash

Gif3

As shown in Figure 15 , we remove first item from payment details and add new item which is paymentDetail6 and it is working fine as shown in Figure 16

Figure 15
Figure 16

As shown in Figure 17 , when user tap on button we are updating paymentDetail4 and paymentDetail5 title value , but as you can see after I clicked on button value not updated what’s wrong, It’s because when we apply new snapshot it identify this item exists in both snapshot old and new using it’s id which is same , when it try to find any difference or value change in these objects it uses == operator but this also check id so for diffable data source no value change

Rule of Thumb 2: Diffable data source uses item hash to identify item. For the same item that exists in both old and new snapshots, diffable data source checks if the item changes by doing an “==” operation with its old and new values.

OR

Use HashValue that identify item instantly and == for the property that is using rendering UI, + both hash and == should unique in one snapshot

Figure 17

As shown in Figure 18 now we change the definition of Equatable now it identify paymentDetail4 in both old and new snapshot using it’s hash which actually it’s id and to check weather there is a change in paymentDetail4 object itself it uses Equtable which is very deep and checking everything that we are rendering in UI to reflect changes. 🐼.

Note: In this blog we are focusing ItemIdentifier value type not reference type like struct. May be I will create another part where we will do experiment with reference type thing as well. Further please add comment if you are not agree with me or I misunderstand something as well so we can learn together

Figure 18

Diffable DataSource Summary

A TableView presents data in the form of sections and items, and an app that displays data in a TableView inserts those sections and items into the view. To support these actions, like inserting, deleting, moving, and updating data within a table view.

When populating a table view in an app, you can create a custom data source that adopts the UITableViewDataSource protocol. To keep the information in the table view current, you determine what data changed and perform a batch update based on those changes, a process that requires careful coordination of inserts, deletes, and moves.

To avoid the complexity of that process, the sample app uses a UITableViewDiffableDataSource object. A diffable data source stores a list of section and item identifiers, which represents the identity of each section and item contained in a table view. These identifiers are stable, meaning they don’t change. In contrast, a custom data source that conforms to UITableViewDataSource uses indices and index paths, which aren’t stable. They represent the location of sections and items, which can change as the data source adds, removes, and rearranges the contents of a collection view. However, with identifiers a diffable data source can refer to a section or item without knowledge of its location within a table view.

As shown in Figure 19 , I removed Equatable at all and used Swift’s default Equatable implementation will check all properties for equality, to avoid extra code

Figure 19

If I remove hash(into hasher: inout Hasher) it will account all properties to create hash, we can do this but for now let’s use our custom or only id to make hashable or generate hashValue

Figure 20

We are calculating hash using id and == we are checking whole object , if you look at the end result you will observe it is not actually update it delete first cell and insert cell at the same time. Also https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/updating_collection_views_using_diffable_data_sources by looking into the documentation

To use a value as an identifier, its data type must conform to the Hashable protocol. Hashing allows data collections such as Set, Dictionary, and snapshots — instances of NSDiffableDataSourceSnapshot and NSDiffableDataSourceSectionSnapshot — to use values as keys, providing quick and efficient lookups. Hashable types also conform to the Equatable protocol, so your identifiers must properly implement equality. For more information, see Equatable.Because identifiers are hashable and equatable, a diffable data source can determine the differences between its current snapshot and another snapshot. Then it can insert, delete, and move sections and items within a collection view for you based on those differences, eliminating the need for custom code that performs batch updates.

Two identifiers that are equal must always have the same hash value. However, the converse isn’t true; two values with the same hash value aren’t required to be equal. This situation is called a hash collision. To increase efficiency, try to ensure that unequal identifiers have different hash values. The occasional hash collision is okay when it’s unavoidable, but keep the number of collisions to a minimum. Otherwise, the performance of lookups in the data collection may suffer.

What you can conclude from this you need to provide same implementation of Hashable and Equtable which is little confuse to me. In the next section we will use direct api of diffable datasource and can learn few things about diffable datasource

Gif4

Directly Calling Diffable DataSource API’s

Insert Item

Delete Item

As shown in Figure 21 , we used Deletes the items with the specified identifiers from the snapshot

mutating func deleteItems(_ identifiers: [ItemIdentifierType]) → Deletes the items with the specified identifiers from the snapshot.The array of identifiers corresponding to the items to delete from the snapshot.

Figure 21

You can delete entire sections

Figure 22

Update Item

We want to achieve this thing where initially 10 rows badge is showing and when we tap on cell it collapse to 5 row and then this thing continues

Gif5
Figure 23
Figure 24
Figure 25

Useful Links

--

--