37
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Diffable DataSource 入門

Last updated at Posted at 2022-09-01

概要

iOS 13 から UICollectionView / UITableView に表示するデータを管理する方法として UICollectionViewDiffableDataSource / UITableViewDiffableDataSource が登場しました。 Diffable DataSource を使うことにより以前までの方法と比べて宣言的に書くことができ、また美しいアニメーションのサポートも受けることができますが、一見すると登場人物が多く複雑に見えます。この記事では、 Diffable DataSource を触ったことがない人でもざっくりとその使い方が理解できるように Diffable DataSource の基礎知識をまとめます。そのための例として Todo アプリを取り上げ、以下の順で作っていきます。その過程で一通り Diffable DataSource の使い方が理解できるようにします。

  • Todo アプリの最小構成を作る
  • Todo を削除できるようにする
  • Todo を編集できるようにする
  • セクションヘッダを追加する
  • Done になっていない Todo のみ表示できるようにする

この記事では iOS 15 以上の UICollectionView を例に使いますが、 UITableView を使う場合や iOS 15 未満の場合でも全体的な考え方は変わらないと思います。また、 UICollectionView では同じことを複数の方法で実現できる場合がありますが、今回は概略を掴むことが目的なので、網羅的に紹介することなくもっとも新しかったり基本的だと思われる方法を使うことにします。

記事中の動作検証は Xcode 14 Beta 5 を使って行っています。

Todo アプリの最小構成を作る

最初に Todo アプリの最小構成を作っていきます。まず、最小構成に必要な登場人物を列挙しておきます。

  • UICollectionView
  • Section
  • Item
    • ItemIdentifier
  • UICollectionView.CellRegistration<UICollectionViewCell, Item>
  • UICollectionViewDiffableDataSource<Section, ItemIdentifier>
  • NSDiffableDataSourceSnapshot<Section, ItemIdentifier>

以下で、それぞれの役割を説明しながら実装していきます。

UICollectionView

UICollectionView はなんらかのデータの集まりを表示するための view です。今回は UICollectionView を使って Todo のリストを表示するので、当然 UICollectionView を画面上に貼っておく必要があります。 UICollectionView を作る上で、例えば UICollectionViewCompositionalLayout を使ってレイアウトを指定する必要がありますが、この記事の主目的はレイアウトではなく Diffable DataSource について理解することなので、 自分でレイアウトを組むのではなく iOS 14 以降で標準で用意されている UICollectionLayoutListConfiguration を使います。 UICollectionLayoutListConfiguration は table view のようなリスト形式の view を表示するための configuration です。これまでに馴染みがない場合でもこの記事を読む上ではそういうものがあるんだと思っておいていただければ大丈夫ですが、詳しくは以下のセッションで詳しく紹介されています。

まずは以下の実装を追加して UICollectionView を画面に表示します。この時点では view を貼っただけで DataSource と繋いでいないので何もデータは表示されません。
今回は UICollectionView をコードで生成していますが、 Storyboard で生成しても大丈夫です。いずれにしてもここまでは view の話であって DataSource には関係ありません。

final class TodoListViewController: UIViewController {
    private var collectionView: UICollectionView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        configureCollectionView()
    }
    
    private func configureCollectionView() {
        // collection view を初期化
        let layout = UICollectionViewCompositionalLayout { sectionIndex, layoutEnvironment in
            let configuration = UICollectionLayoutListConfiguration(appearance: .plain)
            return NSCollectionLayoutSection.list(using: configuration, layoutEnvironment: layoutEnvironment)
        }
        collectionView = UICollectionView(frame: .null, collectionViewLayout: layout)
        
        // collection view を view の全面を覆う形で hierarchy に追加
        view.addSubview(collectionView)
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        collectionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
    }
}

Section / Item / Item Identifier

DataSource を特徴づける型として Section / Item / Item Identifier があります。

Section は CollectionView のセクションのことで、データの表示形式を変えたりデータをまとまりごとに分離したい場合にその数だけ作ります。今回はフラットな Todo リストにするので main という Section を1つだけ作ることにします。

Item は CollectionView の1つのセルに対応するデータです。今回は Todo を表示したいので Todo を表すデータ構造が Item になります。 Item Identifier は Item を一意に特定する id のことで、 Diffable DataSource においては非常に重要な役割を果たします。

Todo という struct を作り、これを Identifieable に準拠させます。 Identifiable の要求は id というプロパティを持つことのみです。この id の型は Hashable であればなんでも大丈夫ですが、その型は associated type である ID に対応するため、これが Todo.ID というように参照できるようになります。

struct Todo: Identifiable {
    var id: UUID // Todo.ID が UUID のエイリアスになる
    var title: String
    var done: Bool
}

final class TodoListViewController: UIViewController {
    enum Section {
        case main
    }

    // ...
}

以上のこのコードではそれぞれ以下の役割を果たします。

  • Section: Section
  • Item: Todo
  • Item Identifier: Todo.ID (UUID)

Identifiable の詳細についてはドキュメントを参照ください。

UICollectionView.CellRegistration<UICollectionViewCell, Item>

DataSource を作成するために CellRegistration が必要なのでこちらを先に作ります。 UICollectionView.CellRegistration<UICollectionViewCell, Item> は、 Item が与えられたときにそれをどのように Cell の表示に反映するかを指定します。コードを見た方が早いと思うので先に載せてしまいます。

final class TodoListViewController: UIViewController {
    override func viewDidLoad() {
        // ...
        configureDataSource()
    }

    private func configureDataSource() {
        let todoCellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Todo> { cell, indexPath, todo in
            var configuration = cell.defaultContentConfiguration()
            configuration.text = todo.title
            cell.contentConfiguration = configuration
            
            cell.accessories = [
                .checkmark(displayed: .always, options: .init(isHidden: !todo.done))
            ]
        }
    }
}

CellRegistration には Cell の設定をするためのクロージャを渡しますが、このクロージャの引数として Cell と Item が渡ってきます。それぞれの型は CellRegistration の型パラメータで指定していまます。今回は CollectionView に UICollectionLayoutListConfiguration を使っているので、これに合わせて Cell は UICollectionViewListCell にします。これは UICollectionViewCell のサブクラスで、リスト表示に使ええ、デフォルトで色々な要素がついてきて便利です。また、前の節で説明した通り今回の CollectionView における Item は Todo です。

UICollectionViewListCell は本題ではないので詳しい説明はしませんが、コードから Todotitle を表示していることや donetrue の場合はチェックマークを表示していそうなことがわかると思います。この設定に使っている contentConfigurationaccessoriesUICollectionViewListCell に特有のプロパティですが、自作の Cell を使う場合などもやることは同じで、受け取った Item の情報を必要に応じて Cell に反映します。

UICollectionViewDiffableDataSource<Section, ItemIdentifier>

いよいよ UICollectionViewDiffableDataSource を追加していきます。 DiffableDataSource の役割は CollectionView に表示するデータを提供することです。と言っても、実装をしていく上で実際にデータを反映するのは次の節で紹介する NSDiffableDataSourceSnapshot なので、実装観点では DiffableDataSource にはデータの全体の流れの調整役のようなイメージを持っておくとわかりやすいかもしれません。

コードは以下のようになります。

final class TodoListViewController: UIViewController {
    private var dataSource: UICollectionViewDiffableDataSource<Section, Todo.ID>!
    private var repository: TodoRepository = .init()

    private func configureDataSource() {
        let todoCellRegistration = // ...

        // DataSource の生成
        dataSource = UICollectionViewDiffableDataSource(
            collectionView: collectionView, // DataSource と CollectionView の紐づけ
            cellProvider: { [weak self] collectionView, indexPath, todoID in // Cell を dequeue して返却
                let todo = self?.repository.todo(id: todoID)
                return collectionView.dequeueConfiguredReusableCell(using: todoCellRegistration, for: indexPath, item: todo)
            }
        )
    }
}

まず、 UICollectionViewDiffableDataSource は型パラメータに Section と Item Identifier を取るので、それぞれここまでに定義した SectionTodo.ID を入れています。イニシャライザには DataSource を紐付けたい CollectionView と、 Cell を返す cellProvider を指定します。 cellProvider は Cell を dequeue して返すクロージャで、引数に Item Identifier が渡ってきます。この Identifier を元に Repository から Item を取得し、その Item と CellRegistration を組み合わせることで Cell を返すことができます。

ここで、実際の Todo データの提供元として TodoRepository を追加しています。もし本当に使うアプリならば Todo は API や DB から取得することになるでしょうが、今回は TodoRepository 初期化時にメモリ上に Todo を30個生成するようにしています。

final class TodoRepository {
    private var todos: [Todo] = (1...30).map { i in
        Todo(id: UUID(), title: "Todo #\(i)", done: false)
    }
    
    func todo(id: Todo.ID) -> Todo? {
        todos.first(where: { $0.id == id })
    }
}

NSDiffableDataSourceSnapshot<Section, ItemIdentifier>

ここまで実装した状態でアプリを実行しても画面は真っ白なままです。ここまでのコードはあくまでデータの流れを作るものであって、まだ実際のデータがどこからも CollectionView に渡されていないからです。データを渡すには NSDiffableDataSourceSnapshot を使います。

具体的には、 NSDiffableDataSourceSnapshotappendSections を呼ぶことで Section を追加し、さらに appendItems で Section に対して Item Identifier を追加します。 Item 自体ではなく Item Identifier を渡すことに注意する必要があり、今回は TodoID のみの配列を Repository に用意しています。作った Snapshot を UICollectionViewDiffableDataSourceapply することで CollectionView にデータが渡されます。

final class TodoRepository {
    // ...

    var todoIDs: [Todo.ID] { todos.map(\.id) }
}

final class TodoListViewController: UIViewController {
    override func viewDidLoad() {
        // ...

        applySnapshot()
    }

    private func applySnapshot() {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Todo.ID>()
        snapshot.appendSections([.main])
        snapshot.appendItems(repository.todoIDs, toSection: .main)
        
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

NSDiffableDataSourceSnapshot には型パラメータでそれぞれ Section と Item Identifier を指定するので、指定した以外の型を渡すことはできず安全になっています。

アプリを実行してみると、想定通り Todo が表示されています。

list.gif

ここまでの実装は以下の gist にあります。

最小構成のまとめ

  • CollectionView / DataSource を特徴付ける型として Section と Item があり、 Item は自身を特定できる型である Item Identifier を持つ
  • DataSource におけるデータ反映の流れを以下のように作る
    • CellRegistration に Item を Cell の表示に反映する処理を書く
    • UICollectionViewDiffableDataSourcecellProvider に Item Identifier が渡されてくるので、そこから Item を取得し、 その Item と CellRegistration を使って Cell を dequeue する処理を書く
  • 実際のデータ反映をするために NSDiffableDataSourceSnapshot を生成し、 Section と Item Identifier を追加して UICollectionViewDiffableDataSourceapply する

Item を Item Identifier として使うことについて

UICollectionViewDiffableDataSource の型パラメータには Item Identifier が使われているのに対し、 CellRegistration には Item が使われています。これは Cell を設定するために Item が必要なため自然ですが、この食い違いのために UICollectionViewDiffableDataSourcecellProvider の中で Item Identifier から Item へ変換する手間が生じています。

この問題を解決するために Item Identifier を Item そのものにしてしまうという手法があります。 Item Identifier は Hashable に準拠していれば大丈夫なので、 TodoHashable にして以下のような修正を加えればデータの流れが Todo で完結することになります。

-struct Todo: Identifiable {
+struct Todo: Identifiable, Hashable {
    var id: UUID
    var title: String
    var done: Bool
}


final class TodoListViewController: UIViewController {
-   private var dataSource: UICollectionViewDiffableDataSource<Section, Todo.ID>!
+   private var dataSource: UICollectionViewDiffableDataSource<Section, Todo>!
  
    // ...   
}

これは一見楽なようですが、

  • 後に紹介する reconfigureItems が動作しなくなる
  • プロパティの値が変わった場合に Identifier が変わってしまうのでアニメーションが想定通り動作しない場合がある
  • hash の計算に時間がかかるようになりパフォーマンスが悪くなる

というデメリットがあることから Apple は推奨していないので、もし新しく CollectionView を作る場合は Item Identifier は Item 自体とは異なる不変かつシンプルな型にしておいたほうが無難だと思います。ただし、もし Item 自体が不変かつシンプルであったり、そうでなくてもプロトタイピング目的であれば Item = Item Identifier の設計でも問題ないようです。詳しくは以下のドキュメントの Populate Snapshots with Lightweight Data Structures の節を参照ください。

Todo を削除できるようにする

これから最小構成に機能を足しつつ Diffable DataSource の機能を理解していきますが、まずは Todo を削除できるようにしてみます。 UICollectionLayoutListConfigurationtrailingSwipeActionsConfigurationProvider に適切な UIContextualAction を渡して、その中で削除処理を実施することにより Cell を左にスワイプして削除できるよくある UI が実現できます。

先に削除処理の追加に必要なすべての差分を示します。

final class TodoRepository {
    // ...

+   func removeTodo(id: Todo.ID) {
+       todos = todos.filter { $0.id != id }
+   }
}

final class TodoListViewController: UIViewController {
    // ...

    private func configureCollectionView() {
        let layout = UICollectionViewCompositionalLayout { sectionIndex, layoutEnvironment in
            var configuration = UICollectionLayoutListConfiguration(appearance: .plain)
+           configuration.trailingSwipeActionsConfigurationProvider = { indexPath in
+               let deleteAction = UIContextualAction(style: .destructive, title: "削除") { [weak self] action, view, completion in
+                   guard let self = self,
+                         let todoID = self.dataSource.itemIdentifier(for: indexPath)
+                   else {
+                       completion(false)
+                       return
+                   }
+                   self.repository.removeTodo(id: todoID)
+                   self.applySnapshot()
+                   completion(true)
+               }
+               
+               return UISwipeActionsConfiguration(actions: [deleteAction])
+           }
            return NSCollectionLayoutSection.list(using: configuration, layoutEnvironment: layoutEnvironment)
        }
    }

    // ...
}

trailingSwipeActionsConfigurationProviderindexPath を引数にして UISwipeActionsConfiguration を返すクロージャを引数にとります。この UISwipeActionConfiguration に削除処理を実装した UIContextualAction を渡しています。

UIContextualAction の3つ目の引数のクロージャで削除処理を行なっています。注意すべき点として、 trailingSwipeActionsConfigurationProvider が Item Identifier ではなく indexPath を渡してくることです。最小構成を作る過程で分かったように基本的には Diffable DataSource を使った CollectionView の実装では Item Identifier が Item を特定するための手段になるのですが、一部の API では indexPath を元に処理を書かないといけないことがあります。このような場合には UICollectionViewDiffableDataSourceitemIdentifier メソッドに indexPath を渡して Item Identifier に変換してもらうことができます。今回は、 TodoRepository に Todo を削除する remoteTodo メソッドを追加していますが、これが Todo.ID を引数に取るので indexPath から変換した id を渡しています。

ちなみに、逆に indexPath がほしい場面で Item Identifier しか分かっていないこともあり、その場合は UICollectionViewDiffableDataSourceindexPath メソッドに Item Identifier を渡します。このように、 indexPath と Item Identifier の変換は UICollectionViewDiffableDataSource にお願いすることになります。

TodoRepository に Todo を削除してもらった後ですが、 Diffable DataSource / CollectionView は表示しているデータの変更を検知したりはしてくれないので、こちらからデータに変更があったことを知らせる必要があります。 Diffable DataSource を使っているため、コードを書くときに差分更新のことを考えずに0からデータを作ってあげればよいので、単に applySnapshot を呼んでいます。

削除時の動作は以下のようになります。

remove.gif

Todo を編集できるようにする

続いて、表示しているデータを編集できるようにします。今回は Todo アプリなので、 Todo を Done にすることを考えます。 Cell がタップされたときに Todo の Done 状態がトグルできるのがよさそうなのでそのように UICollectionViewDelegate を実装していきます。 iOS 13 以降で UICollectionView の DataSource は進化しましたが、 Delegate 周りにはとくに変化はないようです。

必要な差分をすべて示します。

final class TodoRepository {
    // ...

+   func toggleTodo(id: Todo.ID) {
+       for i in todos.indices {
+           if todos[i].id == id {
+               todos[i].done.toggle()
+           }
+       }
    }
}

final class TodoListViewController: UIViewController {
    // ...

    private func configureCollectionView() {
        // ...

+       collectionView.delegate = self
    }

    // ...
    
    private func applySnapshot() {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Todo.ID>()
        snapshot.appendSections([.main])
        snapshot.appendItems(repository.todoIDs, toSection: .main)
        
        dataSource.apply(snapshot, animatingDifferences: true)
    }

+   private func applySnapshotByReconfiguringItem(todoID: Todo.ID) {
+       var snapshot = dataSource.snapshot()
+       snapshot.reconfigureItems([todoID])
+       
+       dataSource.apply(snapshot, animatingDifferences: true)
+   }
}    

+extension TodoListViewController: UICollectionViewDelegate {
+    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+        collectionView.deselectItem(at: indexPath, animated: true)
+        guard let todoID = self.dataSource.itemIdentifier(for: indexPath) else { return }
+        repository.toggleTodo(id: todoID)
+        applySnapshotByReconfiguringItem(todoID: todoID)
+    }
+}           

まずは CollectionView Delegate の collectionView(_:didSelectItemAt:) メソッドでタップを検知します。このメソッドは以前からある古いもので indexPath ベースなので、 DataSource に Todo.ID に変換してもらいます。 TodoRepositorytoggleTodo メソッドを呼んでタップされた Todo の done プロパティを更新した後に snapshot を apply しますが、これまで使ってきた applySnapshot とは異なる方法で apply する applySnapshotByReconfiguringItem メソッドを追加しています。表示する Item Identifier ではなく Item のプロパティが変わった場合は、 NSDiffableDataSourceSnapshotreconfigureItems メソッドにプロパティが変わった Item の Identifier を渡す必要があるためです。手順は、

  • UICollectionViewDataSourcesnapshot メソッドを呼んで現在の NSDiffableDataSourceSnapshot を取得する
  • 取得した NSDiffableDataSourceSnapshotreconfigureItems にプロパティが変更された Item の Identifier を渡す
  • NSDiffableDataSourceSnapshotUICollectionViewDataSourceapply する

となります。注意点として、 reconfigureItems は iOS 15 で追加されたメソッドなので iOS 14 以前では reloadItems メソッドを使う必要があります。これらの違いとしては、 reloadItems は Cell を dequeue し直すのに対して reconfigureItems は既存の Cell をそのまま使うのでよりパフォーマンスがよいという点が挙げられます。ただし、更新時に異なる Cell を使いたい場合などは reloadItems を使って Cell を dequeue し直す必要があります。より詳しくは reconfigureItems のドキュメントを参照ください。

タップ時の動作は以下のようになります。

toggle.gif

セクションヘッダを追加する

Section に対してヘッダを追加してみます。各 Item を Cell で表示しているのに対して、ヘッダは SupplementaryView を使って表示します。 Diffable DataSource においては Cell と SupplementaryView は同じような手順で追加できます。ヘッダを追加するための差分を以下に示します。


final class TodoListViewController: UIViewController {
    // ...

    private func configureCollectionView() {
        let layout = UICollectionViewCompositionalLayout { sectionIndex, layoutEnvironment in
            var configuration = UICollectionLayoutListConfiguration(appearance: .plain)
            configuration.trailingSwipeActionsConfigurationProvider = {
                // ...
            }
+           configuration.headerMode = .supplementary
            return NSCollectionLayoutSection.list(using: configuration, layoutEnvironment: layoutEnvironment)
        }
        
        // ...
    }

    private func configureDataSource() {
        // ...
        
        dataSource = UICollectionViewDiffableDataSource(
            // ...
        )
        
+       let headerRegistration = UICollectionView.SupplementaryRegistration<UICollectionViewListCell>(elementKind: UICollectionView.elementKindSectionHeader) { view, elementKind, indexPath in
+           var configuration = view.defaultContentConfiguration()
+           configuration.text = "Todos"
+           view.contentConfiguration = configuration
+       }
+       
+       dataSource.supplementaryViewProvider = { collectionView, elementKind, indexPath in
+           return collectionView.dequeueConfiguredReusableSupplementary(using: headerRegistration, for: indexPath)
+       }
    }
}

まず、ヘッダを表示するということを UICollectionViewCompositionalLayout に伝える必要があるので、 UICollectionLayoutListConfigurationheaderMode.supplementary に設定します。デフォルトは .none で、ヘッダを表示しないということを意味します。今回は Section が1つしかないですが、複数 Section があって Section によってヘッダを出したり出さなかったりしたい場合は indexPath から Section を取得して headerMode の設定を分岐させることも可能です。

DataSource 側でやることとして、 UICollectionView.SupplementaryRegistration<Supplementary> を生成し、それをもとに UICollectionViewDiffableDataSourcesupplementaryViewProvider を設定しています。この流れば Item を表示する Cell に対して CellRegistrationcellProvider を設定したのとまったく同じになっています。

SupplementaryRegistration の型パラメータには UICollectionReusableView のサブクラスを指定しますが、今回は Cell と同じ UICollectionViewListCell を使っています。 Header に Cell と名のつく View を使うのはやや変な感じがしますが、 UICollectionViewListCell はこのように Cell 以外に対しても使うことができます。これにより SupplementaryRegistration に渡すクロージャの第一引数の viewUICollectionViewListCell になるので、 Cell と同じように defaultContentConfiguration を使うことでそれらしき見た目にしてくれます。ここでは使っていませんが、 accessories も設定できて便利です。

supplementaryViewProvider では、 SupplementaryRegistration を使って dequeueConfiguredReusableSupplementary を呼ぶだけです。この流れは Cell の追加と同様です。

以上でヘッダが表示されます。

header.gif

今回はヘッダのみ追加しましたが、同じような考え方でフッタも追加することができます。

Done になっていない Todo のみ表示できるようにする

最後に、Done になっていない Todo のみの表示と全ての Todo の表示を切り替えられるようにします。やることは Item Identifier を Done になっていない Todo のもののみに絞ってから snapshot を apply するだけなのでとくに新しい要素はないのですが、 Diffable DataSource のアニメーションを確認する意味でもやってみます。

final class TodoRepository {
    private var todos: [Todo] = (1...30).map { i in
        Todo(id: UUID(), title: "Todo #\(i)", done: false)
    }

+   var showsOnlyUndoneTodos: Bool = false
    

-   var todoIDs: [Todo.ID] { todos.map(\.id) }
+   var todoIDs: [Todo.ID] { todos.filter { showsOnlyUndoneTodos ? !$0.done : true }.map(\.id) }
    
    // ...
    
+   func toggleVisibility() {
+       showsOnlyUndoneTodos.toggle()
+   }
}

final class TodoListViewController: UIViewController {
    // ...

    override func viewDidLoad() {
        // ...
        
+       configureNavigationBar()
        
        // ...
    }

+   private func configureNavigationBar() {
+       navigationItem.leftBarButtonItem = UIBarButtonItem(
+           image: UIImage(systemName: "line.3.horizontal.decrease"),
+           primaryAction: UIAction { [weak self] _ in
+               guard let self = self else { return }
+               self.repository.toggleVisibility()
+               self.applySnapshot()
+           }
+       )
+   }

これまで TodoRepositorytodoIDs はすべての Todo の ID を返していましたが、今回追加した showsOnlyUndoneTodos プロパティが true のときは Done になっていない Todo の id のみを返すようにします。

何らかの方法で showsOnlyUndoneTodos を変更できるようにしたいので、 NavigationBar にボタンを追加して、タップ時に値をトグルするようにします。これによって todoIDs が変化するので、その後に applySnapshot を呼んで Snapshot を作り直すことで表示が変化します。 これだけで Diffable DataSource が差分を計算し、アニメーションをつけてくれます。

filter.gif

まとめ

  • CollectionView / DataSource を特徴付ける型として Section と Item があり、 Item は自身を特定できる型である Item Identifier を持つ
  • DataSource におけるデータ反映の流れを以下のように作る
    • CellRegistration に Item を Cell の表示に反映する処理を書く
    • UICollectionViewDiffableDataSourcecellProvider に Item Identifier が渡されてくるので、そこから Item を取得し、 その Item と CellRegistration を使って Cell を dequeue する処理を書く
  • 実際のデータ反映をするために NSDiffableDataSourceSnapshot を生成し、 Section と Item Identifier を追加して UICollectionViewDiffableDataSourceapply する
    • データを更新する際にも、初回の表示と同じように NSDiffableDataSourceSnapshot を新しいデータを使って組み立てて apply すれば OK
  • Item Identifier ではなく特定の Item のプロパティの更新を表示に反映したい場合は UICollectionViewDiffableDataSource から現在の NSDiffableDataSourceSnapshot を取得し、 reconfigureItems メソッドに更新したい Item の Identifier を渡して呼んでから apply する
  • ヘッダやフッタの追加も Cell と同じ流れで行う。 SupplementaryRegistration を生成し、それを UICollectionViewDiffableDataSourcesupplementaryViewProvider の中で使う

参考

37
26
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
37
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?