【Swift】UICollectionViewDiffableDataSourceとNSDiffableDataSourceSnapshot
iOS 13以降で使える UICollectionViewDiffableDataSource
と NSDiffableDataSourceSnapshot
について調べたので備忘録としてまとめます。
どちらもWWDC2019で Compositional Layout
と合わせて発表されましたね。
個人的な印象では、
- データの更新に強くなった
-
Compositional Layout
と組み合わせて複雑なレイアウトの表示とデータ管理が楽になった
こんな感じの印象です。
NSDiffableDataSourceSnapshot
概要
NSDiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType>
データを格納して管理するクラス
SectionIdentifierType: Sectionを定義する型でHashableであること(Enumとかが多そう)
ItemIdentifierType: cellに表示するデータ型でHashableであること
用意されているメソッド、プロパティ
データ管理系
- データの追加・削除(SectionとItem)
- append, insert, delete
- 並び替え(SectionとItem)
- moveSection, moveItem
- リロード(SectionとItem)
- reloadItems, reloadSections
データ参照系
- データ数の取得(SectionとItem)
- public var numberOfItems: Int { get }
- public var numberOfSections: Int { get }
- public func numberOfItems(inSection identifier: SectionIdentifierType) -> Int
- 値(一覧)の取得(SectionとItem)
- public var sectionIdentifiers: [SectionIdentifierType] { get }
- public var itemIdentifiers: [ItemIdentifierType] { get }
- public func itemIdentifiers(inSection identifier: SectionIdentifierType) -> [ItemIdentifierType]
- public func sectionIdentifier(containingItem identifier: ItemIdentifierType) -> SectionIdentifierType?
- indexの取得(SectionとItem)
- public func indexOfItem(_ identifier: ItemIdentifierType) -> Int?
- public func indexOfSection(_ identifier: SectionIdentifierType) -> Int?
使い方
詳細は サンプルコード を見てください。
private enum Section {
case main
}
struct SampleItemModel: Codable, Hashable {
let value: Int
}
// initial data
let list = Array(0..<100).map { SampleItemModel(value: $0) }
var snapshot = NSDiffableDataSourceSnapshot<Section, SampleItemModel>()
snapshot.appendSections([.main])
snapshot.appendItems(list)
UICollectionViewDiffableDataSource
概要
UICollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType>
データをUIに紐付けて表示するクラス
SectionIdentifierType: Sectionを定義する型でHashableであること(Enumとかが多そう)
ItemIdentifierType: cellに表示するデータ型でHashableであること
Initializer
- インスタンス生成
public typealias CellProvider = (UICollectionView, IndexPath, ItemIdentifierType) -> UICollectionViewCell?
public init(collectionView: UICollectionView, cellProvider: @escaping CellProvider)
- ヘッダーとフッター追加
public typealias SupplementaryViewProvider = (UICollectionView, String, IndexPath) -> UICollectionReusableView?
public var supplementaryViewProvider: SupplementaryViewProvider?
用意されているメソッド、プロパティ
データ管理系
- データを適用する
- open func apply(_ snapshot: NSDiffableDataSourceSnapshot, animatingDifferences: Bool = true, completion: (() -> Void)? = nil)
データ参照系
- Snapshotの取得
- open func snapshot() -> NSDiffableDataSourceSnapshot
- cellに表示するデータを取得
- open func itemIdentifier(for indexPath: IndexPath) -> ItemIdentifierType?
- indexPathの取得
- open func indexPath(for itemIdentifier: ItemIdentifierType) -> IndexPath?
元のDataSourceにあって引き継がれた系
- @objc open func numberOfSections(in collectionView: UICollectionView) -> Int
- @objc open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
- @objc open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
- @objc open func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
- @objc open func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool
- @objc open func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath)
- @objc open func indexTitles(for collectionView: UICollectionView) -> [String]?
- @objc open func collectionView(_ collectionView: UICollectionView, indexPathForIndexTitle title: String, at index: Int) -> IndexPath
使い方
詳細は サンプルコード を見てください。
private enum Section {
case main
}
struct SampleItemModel: Codable, Hashable {
let value: Int
}
@IBOutlet weak var advancedCollectionView: UICollectionView! {
didSet {
advancedCollectionView.register(UINib(nibName: "LabelCell", bundle: nil),
forCellWithReuseIdentifier: "LabelCell")
advancedCollectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
}
private var dataSource: UICollectionViewDiffableDataSource<Section, SampleItemModel>! = nil
dataSource = UICollectionViewDiffableDataSource<Section, SampleItemModel>(collectionView: advancedCollectionView) {
// CellProvider
(collectionView: UICollectionView, indexPath: IndexPath, identifier: SampleItemModel) -> UICollectionViewCell? in
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "LabelCell",
for: indexPath) as? LabelCell
else { return UICollectionViewCell() }
cell.set(text: "\(identifier.value)")
return cell
}
dataSource.apply(snapshot, animatingDifferences: false)
まとめ
使ってみてめちゃくちゃ便利だったのでiOS13以降のアプリで積極的に使っていこうと思いました。
ちなみに下記のライブラリを導入すると、
Compositional Layout
UICollectionViewDiffableDataSource
NSDiffableDataSourceSnapshot
がiOS12以前でも使えます。
ただiOS13の挙動と少し違う部分もあるみたいです。(データを追加取得した時とか)
Discussion