目次
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
理由は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
}
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
- 自動的なSnapshotの更新
- 遷移(NSDiffableDataSourceTransaction
// 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
}
}