LoginSignup
8
15

More than 1 year has passed since last update.

【Swift】UICollectionViewDiffableDataSourceとNSDiffableDataSourceSnapshot

Last updated at Posted at 2020-08-02

記事を移動しました。

https://zenn.dev/dd_sho/articles/73393668e7c8e7
今後は上記の記事で更新します。(2021/10/03)

iOS 13以降で使える UICollectionViewDiffableDataSourceNSDiffableDataSourceSnapshot について調べたので備忘録としてまとめます。

サンプルコードはこちら

どちらも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?

使い方

詳細は サンプルコード を見てください。

AdvancedLayoutViewController.swift

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

使い方

詳細は サンプルコード を見てください。

AdvancedLayoutViewController.swift

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の挙動と少し違う部分もあるみたいです。(データを追加取得した時とか)
https://github.com/kishikawakatsumi/IBPCollectionViewCompositionalLayout

8
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
15