The diffable data source section snapshot is an UIKit component introduced in iOS 14. It opens up new possibilities for developers to build various types of hierarchical lists using collection view declaratively.
Even though it is such a powerful component, Apple’s official documentation does not provide much information on how it works and how to use it effectively.
In this article, I would like to share with you some interesting facts about section snapshot that I recently discovered.
With all the being said, let’s get right into it!
The Visual Descriptor
When working with a section snapshot, it is very important to know the hierarchical structure as well as the content of the section snapshot. Therefore, Apple has shipped a visual descriptor alongside the NSDiffableDataSourceSectionSnapshot
generic struct.
By leveraging the visual descriptor, we can easily write an ASCII representation of the section snapshot to the Xcode debug console. What’s even better is that it is extremely easy to use, just call the visualDescription()
instance method and that’s it!
sectionSnapshot.visualDescription()
The image below shows a collection view constructed with a section snapshot (with 3 root items) and its visual description output in the Xcode console.
According to Apple documentation, an asterisk (*
) represents a visible item, a plus sign (+
) represents an expanded item, and a minus sign (-
) represents a collapsed item.
What not being mentioned in the documentation is that the visible item represented by an asterisk (*
) does not mean that the cell represented by the item is currently visible on the screen.
What do I mean by that?
Let’s say you have a very long list and some of the cells are currently off-screen. All of those items that represent the off-screen cells are considered visible as long as they are not children of a collapsed root item.
Whereas, if a child item is currently on screen, but it is not visible due to its root item is collapsed, then the child item is considered non-visible (refer to “Parent 3” section of the above example).
Another interesting fact we get to know from the visual description is that all child items are collapsed by default. In fact, all items in a section snapshot are collapsed by default, including root items. We can expand items in a section snapshot manually using the following code.
sectionSnapshot.expand([sectionItem])
Automatic Snapshot Creation
This is an interesting fact that I discovered while I was trying to replicate the expandable date picker. It is best to explain using an example.
Let’s say you want to construct a simple expandable list as shown below.
Usually, you will first create a snapshot with a main
section, and then apply the desire section snapshot to the main
section.
// Create snapshot with `main` section, then apply to data source.
var snapshot = NSDiffableDataSourceSnapshot<Section, DataItem>()
snapshot.appendSections([.main])
dataSource.apply(snapshot)
// Create and construct a section snapshot, then apply to `main` section in data source.
var sectionSnapshot = NSDiffableDataSourceSectionSnapshot<DataItem>()
sectionSnapshot.append([parentDataItem1])
sectionSnapshot.append(childDataItemArray1, to: parentDataItem1)
dataSource.apply(sectionSnapshot, to: .main, animatingDifferences: false)
The above code is not wrong, however, there is redundancy within it. Apparently, you can totally skip the snapshot creation part and apply the section snapshot to the data source straightaway.
This is because when you apply a section snapshot to the main
section, UIKit will be smart enough to create a snapshot with a main
section automatically for you. Thus the above code can be simplified to the following.
// Create and construct a section snapshot, then apply to `main` section in data source.
var sectionSnapshot = NSDiffableDataSourceSectionSnapshot<DataItem>()
sectionSnapshot.append([parentDataItem1])
sectionSnapshot.append(childDataItemArray1, to: parentDataItem1)
dataSource.apply(sectionSnapshot, to: .main, animatingDifferences: false)
With all that being said, there are still reasons where you want to construct the snapshot manually. One reason that I can think of is to have full control over the order of the snapshot’s sections.
Section Snapshot is Always Available
In one of my previous articles, I mentioned that “If you do not have multi-section data within a collection view section, then you do not need to create a NSDiffableDataSourceSectionSnapshot
“. Therefore, to construct a simple list as shown below, we will not need a section snapshot.
We can construct the list’s data source snapshot using the following code snippet.
var snapshot = NSDiffableDataSourceSnapshot<Section, DataItem>()
snapshot.appendSections([.main])
snapshot.appendItems([
DataItem.child(Child(title: "Child 1 - A")),
DataItem.child(Child(title: "Child 1 - B")),
DataItem.child(Child(title: "Child 1 - C")),
], toSection: .main)
dataSource.apply(snapshot)
As you can see from the code above, a section snapshot is not required, and everything seems to make sense.
But what if I told you that the UICollectionViewDiffableDataSource
class has a snapshot(for:)
function that returns an NSDiffableDataSourceSectionSnapshot
instance? What will we get from this function if we do not apply any section snapshot to the data source?
Let’s find out by adding the following code to the above code snippet.
print(dataSource.snapshot(for: .main).visualDescription())
print("root item count: \(dataSource.snapshot(for: .main).rootItems.count)")
Following is what we get in the Xcode debug console.
Interestingly, we get a section snapshot with 3 collapsed root items. It seems like UIKit has silently created a section snapshot for us when we append the child items to the main
section.
With that, it is safe to say that a section snapshot is always available in a snapshot’s section, we can choose to create it ourselves or let UIKit do the work for us.
The Unwanted Flickering
At the time of writing this article, when you apply a snapshot to the data source, you will see an unwanted flickering glitch. I suspect this might be a bug in the section snapshot. If you are reading this in the future and not able to get the glitch, most probably Apple has fixed it.
It turns out that the flickering is due to the refresh animation. Thus, the most obvious workaround is to disable the animation when applying a snapshot to a data source.
dataSource.apply(snapshot, animatingDifferences: false)
If you would like to keep the animation, another way to work around this is to set the collection view background color to systemBackground
.
collectionView.backgroundColor = .systemBackground
Wrapping Up
The diffable data source section snapshot is a fairly new UIKit component and there is still a lot to be explored.
If you have any interesting discoveries about the section snapshot, I would really like to hear from you. Feel free to let me know in the comment section below, or you can reach out to me on Twitter.
Thanks for reading. 👨🏻💻
Further Readings
- Building an Expandable List Using UICollectionView: Part 1
- Replicate the Expandable Date Picker Using UICollectionView List
- UICollectionView List with Custom Cell and Custom Configuration
- The Modern Ways to Reload Your Table and Collection View Cells
- Declarative UICollectionView List Header and Footer
👋🏻 Hey!
While you’re still here, why not check out some of my favorite Mac tools on Setapp? They will definitely help improve your day-to-day productivity. Additionally, doing so will also help support my work.
- ✨ Bartender: Superpower your menu bar and take full control over your menu bar items.
- ✨ CleanShot X: The best screen capture app I’ve ever used.
- ✨ PixelSnap: Measure on-screen elements with ease and precision.
- ✨ iStat Menus: Track CPU, GPU, sensors, and more, all in one convenient tool.