CodeCompletion.io

Compositional Layouts and Diffable Data Sources

Compositional layouts and diffable data sources are a set of new APIs that were made available in iOS 13 (and augmented in iOS 14) that allow for better usability when presenting data in a Collection View or Table View. Although they can be quite the learning curve when coming from existing ways of managing UICollectionViews and UITableViews up until now (mostly since the new APIs require a change of mentality to truly be productive with them), they do enable simpler and more expressive code when fully embraced. That said, they do not replace any of the older techniques that are still available, but rather enhance them in ways that enable modern layouts without the same amount of custom work that would have been needed previously.

🗄 What is a Data Source?

In the traditional setup for either a collection view or a table view, you have the interface element or view itself that presents all the entries (known as cells) that you see on screen, along with the data that backs up those cells. The way it used to work, you would have needed a collection of elements or model objects — for instance a bunch of people in a contacts app, or menu items in a restaurant’s ordering app — that you ultimately wanted to present on screen. This was done via a special object known as a data source, that was contacted by the collection or table view anytime it was ready to display more cells as the user interacted with it.

Achieved by conforming the data source to the UICollectionViewDataSource or UITableViewDataSource protocols (you might even make use of the delegate protocols UICollectionViewDelegate and UITableViewDelegate as well), this indicated that the object could "speak" the same language, or protocol, that the collection or table view expected. Ultimately, the collection or table view could then ask its data source to indicate how many cells there are, along with which entry should be displayed for which cell, but could also ask its delegate to perform an action when that cell was interacted with by the user. This paradigm has been available since UITableView and UICollectionView were originally introduced, and even long before iOS was even available: on the Mac.

This paradigm works well for highly regular content, but as soon as you have a lot of dynamic reorganization or a custom layout, you’ll start running into problems. You may have even seen such errors: “before the insertion or deletion, there were this many cells, and after the event, there were those many cells”; meanwhile the actual number that should have existed was some other third value. This is a collection or table view’s way of telling the developer something went wrong with how they managed the insertion or deletion of those cells, usually followed by a crash.

When such a thing happens in Xcode, you get a big error so you can find and fix it, but if you are a user out in the wild, the app will just close instantly, without warning. Ultimately, the solution is probably related to the order cells were added or removed, or maybe a step was forgotten; that said, the model layer and the view layer need to be in sync, and if they are not, this entire crop of issues tends to show up.

🔀 Diffable Data Sources

One of the ways Apple has tried to solve this issue for developers is to take some of that work and make it the responsibility of the model to manage, using a new type of data source called a diffable data source. One of the necessary steps is to make entries both Identifiable and Hashable, which allows each model object to indicate if it is related to any other previous instance of the same entry (Identifiable), and if they are, to go one step further and see if they changed since the last time they were displayed (Hashable). The collection or table view can then do a much better job of knowing which model goes with which cell, and if you insert or delete them, or move and reorder them, the view can be smarter about making those changes, without the app's data source needing to do as much manual bookkeeping.

Before diffable data sources, to move a cell on screen the controller (an object coordinating the views and the model, and often was the data source itself) would have had to delete the cell from where it was, and would then create a brand new cell in a new position (using what's called an index path), telling the view about the change. However, if the controller didn't indicate those changes in exactly the right order, problems would crop up. Using diffable data sources allows the developer to express their interests a bit more fluidly, essentially by setting the data source to exist in a particular state, and asking it to figure out where it is now and what it will become by differentiating, or diffing, between the two before and after, animating those changes automatically. This is where the “diffable” comes from in “diffable data sources”.

📰 Compositional Layouts

A companion technology to diffable data sources, compositional layouts allow you to create complex visual interactions between cells. For instance, in the App Store app you might sometimes notice large cards, but other times there will be a smaller table view within one of those cards, or even a section that scrolls vertically. Traditionally, this sort of layout would be very difficult to implement — a developer might even end up embedding a UITableView inside a UICollectionView's cell, causing the entire layout to be very difficult to manage.

With compositional layouts, an iOS developer can design their layout in a way that is not too different than styling <div> and <p> tags in CSS, where a group of cells could be set to scroll horizontally, while others would scroll vertically (this is called orthogonal scrolling) — the cells could even be configured to take up different amounts of space on screen! This allows the developer to create lots of very rich and interesting combinations of layouts that would have otherwise been tedious to implement.

🤝 Benefits All Around

When using a diffable data source, the first thing you may notice is that animations between cells are now nearly automatic, because each model instance is individually hashed and indexed by the framework. This is because snapshots between the old data set and the new data set can be determined really efficiently without storing the entire model multiple times. Whenever you are ready, you can apply a new snapshot on the data source, and indicate if you want it animated or not, and like magic, everything will move to where it needs to move — meaning if a snapshot moves a cell from one section to another, the table view will detect that and animate that movement; the same goes for deletions or insertions.

Without a diffable data source, a simple method of using collection and table views was available — specifically the catch all method of reloadData(), which would tell the view that it should update itself and ask the data source for the most up to date representation of the entries. However, this was without any transitions or smooth animations. Not to mention, it would reload everything, even the parts that didn’t change, which could be inefficient.

Compositional layouts on the other hand, make layouts like those seen in the App Store or Netflix apps so much easier to implement, with much less code, and thus less of a headache for the developer. Combined with a diffable data source, managing this data becomes conceptually possible to revisit and maintain over time.

🔜 Where This Will Lead

Although these are just a few of the new UITableView and UICollectionView APIs introduced with iOS 13, they are likely to make more complex and interactive layouts the standard going forward, specifically because they make them less complex to deal with. Unfortunately for older apps, they would need to wait until they can safely start requiring iOS 13 and newer, as these APIs were unavailable up until then.

The old APIs exist mainly because computers and phones weren’t aways as fast as they are today, so to save calculation time, the app would keep track of changes as they were being made, ultimately letting the view know about any changes once the user returned to it. Now that processors are so much faster and efficient, an app can easily burn through a few extra cycles doing this calculation after the fact, and only when needed, by using a framework-provided algorithm that is heavily optimized, and get the results in a way that is easily conveyed to the collection or table view.

If you are interested in learning more about these APIs, check out Apple’s sample code on both of these topics, which contains tons of examples of really cool screens you can make using these new technologies. The sample code was just updated for iOS 14 since WWDC was not too long ago, so be sure to have the latest beta version of Xcode 12 downloaded if you would like to play around with them.

🧰 Alternative Data Source Paradigms

Somewhat curiously, the Mac has had different paradigms for representing a data source long before iOS was ever a thing. Specifically, NSOutlineView, a subclass of NSTableView, actually asks for an array of contents for a given item, and recursively asks for the children of those items as they become necessary when a row is expanded, without dealing with many of the complexities discussed previously. The Finder, for instance, uses this paradigm to represent files and folders inside of other folders, allowing for lots of data to be shown, just not all at once. This is actually a similar design to what diffable data sources offer, so it's interesting that the model that NSOutlineView uses never directly made the jump to iOS, only to be re-implemented from scratch in a much improved way.

✨ On the New and Shiny

Just because these new technologies exist, however, doesn’t mean you absolutely have to use them. Together, they represent a collection of tools, some of which work better than others depending on the situations being asked of them, so it’s important to use the tool that allows you to build what you want to build. For instance, as soon as UICollectionView came out several years ago, a discussion immediately precipitated around the eventual demise of UITableView. With iOS 14’s introduction of the List type for collection views, those discussions were reignited, now that you can essentially build a table view, and more, with only a collection view.

However, it’s important to remember that these APIs represent increasingly complicated ways of representing your data. If all you need is a simple list, then there is no need to go through the extra effort of implementing that list in a UICollectionView if you know it’ll only ever be a list — the API will be simpler to work with, and you’ll have to jump through less hoops in the process. If you do eventually want to upgrade, the paradigms between them are simple enough that the transition from UITableView to UICollectionView with the same layout should only require minimal changes.

Planning ahead of time can save you transitions in the long run. For instance, if you know that you will want to transition between different view layouts, for instance between rows of rectangles and a grid of squares, then a collection view may make for a better starting point, since you’ll be targeting a specific conclusion right from the beginning.

As a side note, when collection views were released, the underlying implementation of table views was actually swapped out with a shared implementation, simply because collection views are a natural super set of table views. That said, it's important to remember that if you really wanted, you could implement the same functionality completely from scratch, as there is very little magic actually happening behind the scenes, just carefully crafted and optimized algorithms.

Ultimately though, if you do find yourself having written something completely from scratch only to find out Apple released a new API that supersedes it, it’s likely a good idea to move over to that API as soon as you can, as it will likely be maintained on your behalf, letting you focus on the parts of your app that make it unique, rather than working on aspects that would better fit within a framework.

🎙 More From This Episode