Streaming is available in most browsers,
and in the Developer app.
-
Advances in diffable data sources
Diffable data sources dramatically simplify the work involved in managing and updating collection and table views to create dynamic and responsive experiences in your apps. Discover how you can use section snapshots to efficiently build lists and outline collection views for iOS and iPadOS and provide support for implementing the sidebar in an iPad app. We'll also show you how to simplify cell reordering using UICollectionViewDiffableDataSource to help you streamline your code and build app interfaces more quickly. This session builds on 2019's “Advances in UI Data Sources,” which you may want to check out first.
Resources
Related Videos
WWDC21
WWDC20
WWDC19
-
Download
Hello and welcome to WWDC.
Welcome. My name is Steve Breen, and I'm an engineer on the UIKit team. And in this video, we're going to chat a bit about advances in Diffable Data Source for iOS 14.
So, before we dive into our content, I want to highlight a portion of the companion sample project for this video entitled "Emoji Explorer." This sample has a lot of interesting components baked right into its design.
Now, in this first section, there's a horizontally scrolling grid of emoji. This is a pretty common design element in many of today's apps. Okay, so, this section in the middle of Emoji Explorer is especially novel. It's an expandable-collapsible outline-styled UI which is new in iOS 14.
And finally, in this last section, there's a design that looks suspiciously like a UITableView right here in the middle of our UICollectionView. Okay, that's Emoji Explorer, and we'll be referring back to this throughout our video.
Okay. So, to review, Diffable Data Source, which was introduced in iOS 13, greatly simplifies the management of UI state through the addition of a new snapshot data type.
Snapshots encapsulate the entire UI state via the use of unique section and item identifiers. So, when updating a UICollectionView, we first create a new snapshot, populate it with the current UI state, and apply it to the data source.
Diffable Data Source computes the differences and animates things automatically without any additional work needed from the application developer.
So we covered this API in detail in the WWDC 2019 video, "Advances in UI Data Sources," and I encourage you to check out that video to learn more.
So, for iOS 14, we've built on the foundation of Diffable Data Source with two new features: Section Snapshots and first class Reordering Support. Let's chat a bit about Section Snapshots.
For iOS 14, we're adding a new section snapshot right alongside the existing snapshot type we call "section snapshots." And as the name implies, section snapshots encapsulates the data for a single section in a UICollectionView. There are two reasons for this enhancement. First, to allow data sources to be more composable into section-sized chunks.
And second, to allow modeling of hierarchical data which is needed to support rendering outline-style UIs, a common visual design found all throughout iOS 14.
So let's go back to Emoji Explorer and see how section snapshots are used to build this out in our sample app.
First, in our horizontally scrolling section, we are using a single section snapshot to completely model the content found here.
Next, in our second section where we see this expandable-collapsible outline-style section, a second section snapshot is used to model this hierarchical data. And finally, in our list section, we again model this section's content with a third section snapshot.
So for Emoji Explorer, we compose our Diffable Data Source from three distinct section snapshots each representing a single section's content. Let's take a look at some API. So, here's a snippet of the new Section Snapshot API, and check out the SDK for the entire API.
So, please note that we'll use the term "snapshot" when we're referring to the original snapshot type introduced in iOS 13, which includes sections and item identifiers.
And we'll use the term "section snapshot" to refer to this new to iOS 14 type. So, glancing at this new section snapshot type, we see that it's generic over the item type.
Note there's an absence of any kind of section identifier type. section snapshots just inherently don't know what section they represent.
To add content to our section snapshot, we'll use the 'append' API.
Now, notice the optional parent parameter. When supplied, this allows us to create parent-child relationships in the section snapshot needed to model hierarchical data.
Now, we've added two new APIs to UICollectionViewDiffableDataSource to accommodate the new section snapshot type.
First, we have a new 'apply' method which takes a section snapshot and a section identifier.
Our second new API allows us to retrieve a section snapshot representing a particular section's contents. So next, let's flesh out a code snippet showing how to use snapshots and section snapshots together to build up our Collection View's content. First, we'll add sections in the order we desire by applying a snapshot to our Diffable Data Source.
And here we see, we have the sections in a particular order: 'recent,' 'top,' and 'suggested.' Now, after we have the desired section order, we're going to populate the items for each of these sections by applying section snapshots directly to each section. Let's take a look at how to create hierarchical data using this Section Snapshot API. First, we'll append some root items to our sections. Smiley, nature, food, et cetera using the 'append' API.
Recall the 'append' method has an optional 'parent' parameter. So when that's not supplied, that means apply these items to the root. So, to configure our parent-child relationship, we'll append some child items into a parent item. In this example, the parent item is "food." And that's it. We've created a section snapshot which models our application's hierarchical data. Okay. So, it should be clear by now, section snapshots are capable of representing hierarchical data.
Now, at times, it's very convenient to reason about just a portion of this hierarchical data.
So, in this code snippet, we're interested in getting all the children related to a particular parent item, optionally including the parent in a resultant section snapshot.
Okay. So, next up, let's chat a bit about expansion state.
So expansion state is managed as part of a section snapshot's state. And when building up a snapshot for display, you can easily determine whether or not a child content is initially visible by setting that item's parent expansion state.
You can also query a snapshot to determine if an item is expanded or collapsed. Note that if you mutate a section snapshot's expansion state, this won't apply until you actually apply that to the Diffable Data Source. Okay, so when a user interacts with an outline-styled UI which is configured with the new Cell Outline Disclosure Accessory, the framework will automatically update the section snapshot with the new expansion state and apply that section snapshot to the data source. Now, often it's useful to be notified about expansion state changes caused by these user interactions. For example, you may have a design that requires that a particular parent never collapse.
To support this, Diffable Data Source has new APIs for giving applications programmatic control over expansion state changes caused by user interactions. Okay, so here are these new APIs.
First, we notice that Diffable Data Source has a new property called "SectionSnapshotHandlers." The new SectionSnapshotHandlers type is a struct which is generic over item and contains five optional closures. To handle our previously mentioned requirement, we could provide a shouldCollapseItem handler which will return 'false' for a particular parent to avoid collapsing the outline. Now, we also provide support for lazy loading of expensive content with the API snapshotForExpandingParent.
So this is useful to minimize the amount of content loaded in the initial section snapshot when getting that content is expensive. So, you can load that content as needed depending on the current child snapshot's state. So, that wraps up Section Snapshots. One of the advances Diffable Data Source brings is the ability to model our Collection View's data with unique item identifiers. With these unique item identifiers, it is possible for the framework to automatically commit reordering changes on the application's behalf based on user interactions. But this isn't really enough. Our application needs to be notified that a user-initiated reordering interaction took place so that it can persist the new visual order to the application's backing store, which is its final source of truth. So, to support reordering, Diffable Data Source now has a new property: reorderingHandlers.
This a struct which contains three optional closures: canReorderItem, willReorder, and didReorder. So, to enable reordering via this new API, you'll first need to supply a canReorderItem closure. This will be called when a user attempts to start a reordering interaction. If it returns 'true,' the reordering interaction is allowed to begin. When the user is done with the reordering interaction, the didReorder closure is called to allow your application to commit the new ordering state to your application's source of truth. Note that you must provide both the canReorderItem and didReorder closure to enable the reordering feature. Okay. So, the didReorder closure passes your application a new type: NSDiffableDataSourceTransaction.
So, transactions supply all the information needed to reason about the update being performed against the Diffable Data Source. First, let's check out the NSDiffableDataSourceTransaction. This type supplies four basic pieces of information. First, we have the initialSnapshot. This is the state of the Diffable Data Source before the update is applied.
Next, we have the finalSnapshot. Now, this is the state of the Diffable Data Source after the update is applied. And we can use these item identifiers from this snapshot directly to determine the new ordering which we need to commit to our application's source of truth. Additionally, we can see the Swift standard lib CollectionDifference is also supplied. And if your application has a data type such as 'Array' for the source of truth, we can apply that CollectionDifference directly to that. And, lastly, we see a list of section transactions that provide per-section details about all the sections involved in this reordering update. Section transactions are quite similar. They have one section transaction supplied for each section involved in the reordering update. First off, we can inspect the sectionIdentifier this sectionTransaction has been applied to. And we also see we have the initial and final section snapshots, along with the CollectionDifference specific to this section's update. Okay. So, let's check out an example. Here, our backing store is an array of items supplying the source of truth for a single section Collection View.
Using the Swift standard lib CollectionDifference supplied with the transaction, we'll create a new backing store and update our source of truth directly.
Well, that wraps up our video today covering all the advances we've brought to Diffable Data Source for iOS 14.
Download the sample app. It's full of examples of how to use these new APIs. And use this code as a starting point to update your apps to take advantage of these new APIs. And watch the related videos mentioned throughout this talk to go deeper.
Thanks for watching.
-
-
3:24 - NSDiffableDataSourceSectionSnapshot
// NSDiffableDataSourceSectionSnapshot public struct NSDiffableDataSourceSectionSnapshot<ItemIdentifierType> where ItemIdentifierType : Hashable { public init() public init(_ snapshot: NSDiffableDataSourceSectionSnapshot<ItemIdentifierType>) public mutating func append(_ items: [ItemIdentifierType], to parent: ItemIdentifierType? = nil) public mutating func insert(_ items: [ItemIdentifierType], before item: ItemIdentifierType) public mutating func insert(_ items: [ItemIdentifierType], after item: ItemIdentifierType) public mutating func delete(_ items: [ItemIdentifierType]) public mutating func deleteAll() public mutating func expand(_ items: [ItemIdentifierType]) public mutating func collapse(_ items: [ItemIdentifierType]) public mutating func replace(childrenOf parent: ItemIdentifierType, using snapshot: NSDiffableDataSourceSectionSnapshot<ItemIdentifierType>) public mutating func insert(_ snapshot: NSDiffableDataSourceSectionSnapshot<ItemIdentifierType>, before item: (ItemIdentifierType)) public mutating func insert(_ snapshot: NSDiffableDataSourceSectionSnapshot<ItemIdentifierType>, after item: (ItemIdentifierType)) public func isExpanded(_ item: ItemIdentifierType) -> Bool public func isVisible(_ item: ItemIdentifierType) -> Bool public func contains(_ item: ItemIdentifierType) -> Bool public func level(of item: ItemIdentifierType) -> Int public func index(of item: ItemIdentifierType) -> Int? public func parent(of child: ItemIdentifierType) -> ItemIdentifierType? public func snapshot(of parent: ItemIdentifierType, includingParent: Bool = false) -> NSDiffableDataSourceSectionSnapshot<ItemIdentifierType> public var items: [ItemIdentifierType] { get } public var rootItems: [ItemIdentifierType] { get } public var visibleItems: [ItemIdentifierType] { get } }
-
4:20 - UICollectionViewDiffableDataSource Additions for Section Snapshots
// UICollectionViewDiffableDataSource additions for iOS 14 extension UICollectionViewDiffableDataSource<Item, Section> { func apply(_ snapshot: NSDiffableDataSourceSectionSnapshot<Item>, to section: Section, animatingDifferences: Bool = true, completion: (() -> Void)? = nil) func snapshot(for section: Section) -> NSDiffableDataSourceSectionSnapshot<Item> }
-
4:43 - Using Snapshots and Section Snapshots together
// Example of using snapshots and section snapshots together func update(animated: Bool=true) { // Add our sections in a specific order let sections: [Section] = [.recent, .top, .suggested] var snapshot = NSDiffableDataSourceSnapshot<Section, Item>() snapshot.appendSections(sections) dataSource.apply(snapshot, animatingDifferences: animated) // update each section's data via section snapshots in the existing position for section in sections { let sectionItems = items(for: section) var sectionSnapshot = NSDiffableDataSourceSectionSnapshot<Item>() sectionSnapshot.append(sectionItems) dataSource.apply(sectionSnapshot, to: section, animatingDifferences:animated) } }
-
5:18 - Creating hierarchical data with NSDiffableDataSourceSectionSnapshot
// Create hierarchical data for our Outline var sectionSnapshot = ... sectionSnapshot.append(["Smileys", "Nature", "Food", "Activities", "Travel", "Objects", "Symbols"]) sectionSnapshot.append(["🥃", "🍎", "🍑"], to: "Food")
-
6:01 - Child Section Snapshots
let childSnapshot = sectionSnapshot.snapshot(for: parent, includingParent: false)
-
6:11 - Section Snapshot Expand / Collapse API
struct NSDiffableDataSourceSectionSnapshot<Item: Hashable> { func expand(_ items: [Item]) func collapse(_ items: [Item]) func isExpanded(_ item: Item) -> Bool }
-
7:21 - Section Snapshot Handlers
// Section Snapshot Handlers: handling user interactions for expand / collapse state changes extension UICollectionViewDiffableDataSource { struct SectionSnapshotHandlers<Item> { var shouldExpandItem: ((Item) -> Bool)? var willExpandItem: ((Item) -> Void)? var shouldCollapseItem: ((Item) -> Bool)? var willCollapseItem: ((Item) -> Void)? var snapshotForExpandingParent: ((Item, NSDiffableDataSourceSectionSnapshot<Item>) -> NSDiffableDataSourceSectionSnapshot<Item>)? } var sectionSnapshotHandlers: SectionSnapshotHandlers<Item> }
-
8:52 - Reordering Handlers
// Diffable Data Source Reordering Handlers extension UICollectionViewDiffableDataSource { struct ReorderingHandlers { var canReorderItem: ((Item) -> Bool)? var willReorder: ((NSDiffableDataSourceTransaction<Section, Item>) -> Void)? var didReorder: ((NSDiffableDataSourceTransaction<Section, Item>) -> Void)? } var reorderingHandlers: ReorderingHandlers }
-
9:45 - Diffable Data Source Transactions
// NSDiffableDataSourceTransaction struct NSDiffableDataSourceTransaction<Section, Item> { var initialSnapshot: NSDiffableDataSourceSnapshot<Section, Item> { get } var finalSnapshot: NSDiffableDataSourceSnapshot<Section, Item> { get } var difference: CollectionDifference<Item> { get } var sectionTransactions: [NSDiffableDataSourceSectionTransaction<Section, Item>] { get } } struct NSDiffableDataSourceSectionTransaction<Section, Item> { var sectionIdentifier: Section { get } var initialSnapshot: NSDiffableDataSourceSectionSnapshot<Item> { get } var finalSnapshot: NSDiffableDataSourceSectionSnapshot<Item> { get } var difference: CollectionDifference<Item> { get } }
-
11:07 - Diffable Data Source Reordering Example
dataSource.reorderingHandlers.didReorder = { [weak self] transaction in guard let self = self else { return } if let updateBackingStore = self.backingStore.applying(transaction.difference) { self.backingStore = updatedBackingStore } }
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.