Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2020 SwiftUI & UI Frameworks

WWDC20 · 12 min · SwiftUI & UI Frameworks

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.

Watch at developer.apple.com ↗

Transcript all transcripts

Code shown on screen · 10 snippets

NSDiffableDataSourceSectionSnapshot swift · at 3:24 ↗
// 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 }
}
UICollectionViewDiffableDataSource Additions for Section Snapshots swift · at 4:20 ↗
// 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>
}
Using Snapshots and Section Snapshots together swift · at 4:43 ↗
// 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)
   }
}
Creating hierarchical data with NSDiffableDataSourceSectionSnapshot swift · at 5:18 ↗
// Create hierarchical data for our Outline

var sectionSnapshot = ...

sectionSnapshot.append(["Smileys", "Nature", 
                        "Food", "Activities",
                        "Travel", "Objects", "Symbols"])

sectionSnapshot.append(["🥃", "🍎", "🍑"], to: "Food")
Child Section Snapshots swift · at 6:01 ↗
let childSnapshot = sectionSnapshot.snapshot(for: parent, includingParent: false)
Section Snapshot Expand / Collapse API swift · at 6:11 ↗
struct NSDiffableDataSourceSectionSnapshot<Item: Hashable> {
   func expand(_ items: [Item])
   func collapse(_ items: [Item])
   func isExpanded(_ item: Item) -> Bool
}
Section Snapshot Handlers swift · at 7:21 ↗
// 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>
 
}
Reordering Handlers swift · at 8:52 ↗
// 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
}
Diffable Data Source Transactions swift · at 9:45 ↗
// 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 }
}
Diffable Data Source Reordering Example swift · at 11:07 ↗
dataSource.reorderingHandlers.didReorder = { [weak self] transaction in 
   guard let self = self else { return }

   if let updateBackingStore = self.backingStore.applying(transaction.difference) {
      self.backingStore = updatedBackingStore
   }
}

Resources