Diffable Data Sourcesの応用

Advances in diffable data sources

https://developer.apple.com/videos/play/wwdc2020/10045/

Sessionの概要

Diffable Datta SourcesはCollectionやTableViewの管理及び更新に必要な作業を劇的に簡素化し、
ダイナミックでレスポンシブルなUXを実現します。
Section Snapshotを使って、iOSやiPadOSでリストやアウトラインのCollection Viewを効率的に構築し、
iPadのサイドバーの実装をサポートします。 
また、UICollectionViewDiffableDataSourcesを使ってCellの並び替えを簡素化する方法を説明されている

iOS14で追加されたDiffable Data SourceのAPI

  • Section Snapshot
  • Reordering Support

Section Snapshot

https://developer.apple.com/documentation/uikit/nsdiffabledatasourcesectionsnapshot
UICollectionViewの単一SectionのデータのSnapshotをとる
理由は2つあり、
  • Section単位で区切ることが出来、データソースを構築可能なこと
  • 並び替えが必要なアウトラインStyleのUIに対して、階層構造を持つデータのモデリングが可能なこと
    • これによりリストの展開や折りたたみが可能となる

UICollectionViewDiffableDataSourcesに追加されたAPI

// 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>
}

Sample Code

// 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)
   }
}

階層構造を持つデータの実装方法

// Create hierarchical data for our Outline

var sectionSnapshot = ...

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

sectionSnapshot.append(["🥃", "🍎", "🍑"], to: "Food")

展開、折りたたみ

struct NSDiffableDataSourceSectionSnapshot<Item: Hashable> {
   func expand(_ items: [Item])
   func collapse(_ items: [Item])
   func isExpanded(_ item: Item) -> Bool
}
expansion stateにより、Section自身や親のSectionが展開されているかどうかを管理することが出来る

Section Snapshot Handler

// 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 Support

// 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 Transaction

// 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 }
}

Sample Code

データソースを並び替えした後にコールされるclosure
並び替え後の差分を NSDiffableDataSourceSectionTransaction から取得して前の状態に戻すことを可能にしている
dataSource.reorderingHandlers.didReorder = { [weak self] transaction in 
   guard let self = self else { return }

   if let updateBackingStore = self.backingStore.applying(transaction.difference) {
      self.backingStore = updatedBackingStore
   }
}
最新情報をチェックしよう!