UICollection Compositional Layout Part 4 DataSource

Ali Akhtar
12 min readSep 24, 2021

As shown in Figure 1 data sources today, in UITableView and CollectionViews? So here we see an example implementation of the UICollectionView data source.

Now, if you’ve worked with UITableView or CollectionViews, you’ve seen this before. Here, we’re providing three of the required two methods in this protocol. And it’s pretty straightforward, right? We have asked about the number of sections and the number of items in each of those sections. And as content renders, we’re going to ask for cells. Pretty straightforward. Now apps get more and more complex every year. They do more things. Our users demand more features. And oftentimes, these data sources are backed by complex controllers inside of our app. And these controllers can do a variety of things. They can interact with Core Data, and they could talk to web service. They just do a number of different things. And I want to visualize this real quick. And let’s show a conversation between your UI layer and this controller layer that’s doing all this heavy lifting to get your data. All right? And the conversation starts out very civil. It’s like, “Hey, give me a number of items in a section, or give me a cell as we render content.” Very straightforward. And so far, smooth sailing. But things get more complicated over time, right? You get — like let’s say this controller has a web service request that it gets a response from, right? It’s like, oh, I’ve got data for your tweets or whatever, right? Well, now this controller layer, which is complex unto itself, lets the world know, “Hey, I changed. Something changed.” All right, so here’s where things get a bit more complex, right? So now, it’s up to the UI layer to decide, “Hey, things changed. Now, I need to — can change this into updates for our UI layer. And this involves all the mutations that have to occur against TableView and CollectionView. And how to construct those batch updates properly, and mutate your backing store, all those kinds of things. But sometimes, no matter how hard you try — You know, things go wrong as shown in Figure 2. It’s an imperfect world. It’s not an uncommon thing. And it’s really frustrating, right? You hit this, and you’re like, “All right. What did I do wrong?” It’s me. And as you dig through your code, okay, fine. You Google on Stack Overflow, see what’s going on. And eventually, you might get frustrated and just call reloadData. And we talked about this last year, and that’s fine. That’s correct. Your app looks okay. But when you call reloadData, you get this non-animated effect. And it detracts from the user experience.

Figure 1
Figure 2

Problem

What’s the problem? Well, the problem here is where’s our truth? You know? I mean, who got his? And who has all the answers? And the big issue here is that our data controller — or it’s acting as a data source — has its own version of the truth, which changes over time. And the UI has a version of the truth. And the UILayerCode is responsible for mitigating that, making sure that it’s always in sync. As we saw, it’s sometimes hard. So our current approach is error prone. And primarily because there’s just no notion of a centralized truth. All right, so that’s the state-of-the-art. That’s where we are today. But where are we going? Well, I’m happy to announce that for iOS, TVoS, and MacOS this year, we’re bringing a brand new approach.And we’re calling this DiffableDataSource.

Figure 3

DiffableDataSource (New approach)

No performBatchUpdates. Let’s go on. And along with it, all the crashes, hassles, complexity, all of the stuff that you don’t want to deal with, has been jettisoned. Instead, we have a single method we call Apply. What is Apply? Apply is simple, automatic, hassle-free diffing. So we do this with a brand new construct we call a Snapshot. And it’s a very simple idea. It’s effectively the truth of the current UI state. And instead of IndexPaths, it’s an association or a collection of section identifiers that are all unique and item identifiers. And you update these not with IndexPaths, with identifiers.

Figure 4
Figure 5

SnapShot

. So we’ve got these FOO, BAR, and BIF onscreen, right. And that’s what we’re interacting with. These are identifiers in our app. And let’s say that our controller changed. And now we’ve got this brand new Snapshot that we want to apply.But this is our current Snapshot. How do we get from our new truth to the current Snapshot? Well, we can see here we’ve configured a brand new Snapshot with BAR, FOO, and BAZ. And we have some items that are coming along for the ride that have just changed order, and then a new item coming in.So conceptually an Apply knows about the current state and knows about the new state, which are going to apply to the UI element.

Figure 6.1
Figure 6.2
Figure 6.3
Figure 6.4

so how do we do this? Well, we have four classes across all the platforms. For iOS and TVoS, we have UICollectionViewDiffableDataSource and UITableViewDiffableDataSource. And then on the Mac, we have NSCollectionViewDiffableDataSource. And common for all the platforms is this Snapshot class, which is responsible for the current UIState NSDiffableDataSourceSnapshot.

Figure 7

Getting Started

As shown in Figure 8 we created Tweet Feed collection view Cell , so it prerequisite you know autolayout and how to create custom view through Interface Builder

Figure 8

In Figure 9 we created TweetFeed model. Note it is conform to Hashable which is mandatory for diffable data source. To use TweetFeed as a diffable data source , you need to add Hashable conformance

Figure 9

In Figure 10, all code related to build layout and data sources

Figure 10

In Figure 11.1 we added tweet3 with same value as tweet2 and application got crash Fatal: supplied item identifiers are not unique. because current logic to create hash Value using data in each property , so

Figure 11.1
Figure 11.2

Hashable

As you can see in Figure 5

  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 12

To use TweetFeed in a diffable data source. The recommended approach to solve this is to add a UUID string to the type to serve as a hash value. As long as any unique value is used as hash , you are good to go. For every TweetFeed instance. As shown in Figure 13 we make tweet id as a hashvalue which will be unique to every tweet. If server sending some unique key you can also used that but we aware backend guy don't send same unique key which usually happen if you have a pagination API where you are doing infinite loading stuff.

Figure 13

As shown in Figure 14.1 and14.2 we added one button and we move sapshot logic to some other method

Figure 14.1
Figure 14.2

Insert Item

As shown in Figure 15, we add new item with animation and see no perform batch which is cool right.

  • To show the animation initially I show only one tweet
  • On insert, we first fetch old snapshot and append new tweets and tell apply new snapshot, with animation in a declarative way
  • And diffable data source did it’s magic
Figure 15
Gif 1

Delete Item

As shown in Figure 13.1 and 16.2 we deleted item with animation. One thing we did we make data that populated the snapshot as a property

Figure 16.1
Figure 16.2
Gif 2

Update Item

As shown in Figure 17.1 we update “Imran Khan” tweet likes to 2000 and we called reload item and given whole tweet object as an identifier. But we got crash and it is saying “Invalid item identifier specified for reload”

Reloads the data within the specified items in the snapshot.
Declaration mutating func reloadItems(_ identifiers: [ItemIdentifierType])
Parameters identifiers The array of identifiers corresponding to the items to reload in the snapshot.

Figure 17.1
Figure 17.2

This sums of all, object before update have the same hashvalue and both are equal but after update hashValue is same but it is not equal anymore. In addition to this when a data source compares between two snapshots, it’s going to look at whether two instances of the same object have changed in other ways. For it to be you need to provide Equitable conformance. In our case two objects same if they have all value equals, so if we update likeCount value both object will not be the same

Figure 18

As shown in Figure 19 we added Equatable conformance, Now two snapshots are equal if the id of both are same

Figure 19

Now you are thinking like if we tap on button “Imran Khan” likes will change to 2000 but if we see logs snapshot likeCount value still 1000 and our own manage data has likeCount value 2000

When you tell a snapshot to reload a certain item, it does not read in the data of the item you supply! It simply looks at the item, as a way of identifying what item, already in the data source, you are asking to reload.

(So, if the item you supply is Equatable to but not 100% identical to the item already in the data source, the “difference” between the item you supply and the item already in the data source will not matter at all; the data source will never be told that anything is different.)

When you then apply that snapshot to the data source, the data source tells the table view to reload the corresponding cell. This results in the data source’s cell provider function being called again.

OK, so the data source’s cell provider function is called, with the usual three parameters — the table view, the index path, and the data from the data source. But we’ve just said that the data from the data source has not changed. So what is the point of reloading at all?

The answer is, apparently, that the cell provider function is expected to look elsewhere to get (at least some of) the new data to be displayed in the newly dequeued cell. You are expected to have some sort of “backing store” that the cell provider looks at. For example, you might be maintaining a dictionary where the key is the cell identifier type and the value is the extra information that might be reloaded. as shown in Figure 21 and Gif 3

This must be legal, because by definition the cell identifier type is Hashable and can therefore serve as a dictionary key, and moreover the cell identifiers must be unique within the data, or the data source would reject the data (by crashing). And the lookup will be instant, because this is a dictionary.

Figure 20
Figure 21
Gif3

As shown in Figure 22 what we did we update data that we are maintaining and then we create sections and items but still likesCount not updated , snapshot updated but when diffable datasource is applying diff for them “Imran khan” tweet object is same since it is comparing with id. Now you are confused what the heck it is

Figure 22

Now what i did i added likeCount in Equtable conformance now diff find snapshot data differences

Figure 23

As shown in Figure 24 , in this case we put likeCount in hasValue ad still working ,

Figure 24

So now we delete item but before delete we updated likeCount so both snapshot diff and it can’t find any , in this case it will not crash

Figure 25

I usually follow this approach

  • write your hash function combining all the values that can change in the view ex.
  • Create new snapshot and apply it will automatically find optimized diff and reader UI
Figure 26
Figure 27

One more hack to update data 😎here we modifier older snapshot

Figure 28

You can move the Item as well

Figure 29

In short

  • No perform batch updated ad only apply
  • Note diffable datasource diff operation complexity is O(N)

As shown below we called apply on background queue as it is supported

Figure 30

If you choose this model to call Apply from the background queue, be consistent. Just always call it from the background queue. You never want to mix and match calling it from a background queue or the main queue. Just always do it the same way.

And we’re good citizens. We’ll complain about this if you get it wrong. as shown i Figure 31

Figure 31

Useful links

https://developer.apple.com/videos/play/wwdc2019/220/

--

--

Ali Akhtar

Senior iOS Engineer | HungerStation | Delivery Hero