概要
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
は本題ではないので詳しい説明はしませんが、コードから Todo
の title
を表示していることや done
が true
の場合はチェックマークを表示していそうなことがわかると思います。この設定に使っている contentConfiguration
や accessories
は UICollectionViewListCell
に特有のプロパティですが、自作の 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 を取るので、それぞれここまでに定義した Section
と Todo.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
を使います。
具体的には、 NSDiffableDataSourceSnapshot
の appendSections
を呼ぶことで Section を追加し、さらに appendItems
で Section に対して Item Identifier を追加します。 Item 自体ではなく Item Identifier を渡すことに注意する必要があり、今回は Todo
の ID
のみの配列を Repository に用意しています。作った Snapshot を UICollectionViewDiffableDataSource
に apply
することで 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 が表示されています。
ここまでの実装は以下の gist にあります。
最小構成のまとめ
- CollectionView / DataSource を特徴付ける型として Section と Item があり、 Item は自身を特定できる型である Item Identifier を持つ
- DataSource におけるデータ反映の流れを以下のように作る
-
CellRegistration
に Item を Cell の表示に反映する処理を書く -
UICollectionViewDiffableDataSource
のcellProvider
に Item Identifier が渡されてくるので、そこから Item を取得し、 その Item とCellRegistration
を使って Cell を dequeue する処理を書く
-
- 実際のデータ反映をするために
NSDiffableDataSourceSnapshot
を生成し、 Section と Item Identifier を追加してUICollectionViewDiffableDataSource
にapply
する
Item を Item Identifier として使うことについて
UICollectionViewDiffableDataSource
の型パラメータには Item Identifier が使われているのに対し、 CellRegistration
には Item が使われています。これは Cell を設定するために Item が必要なため自然ですが、この食い違いのために UICollectionViewDiffableDataSource
の cellProvider
の中で Item Identifier から Item へ変換する手間が生じています。
この問題を解決するために Item Identifier を Item そのものにしてしまうという手法があります。 Item Identifier は Hashable
に準拠していれば大丈夫なので、 Todo
を Hashable
にして以下のような修正を加えればデータの流れが 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 を削除できるようにしてみます。 UICollectionLayoutListConfiguration
の trailingSwipeActionsConfigurationProvider
に適切な 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)
}
}
// ...
}
trailingSwipeActionsConfigurationProvider
は indexPath
を引数にして UISwipeActionsConfiguration
を返すクロージャを引数にとります。この UISwipeActionConfiguration
に削除処理を実装した UIContextualAction
を渡しています。
UIContextualAction
の3つ目の引数のクロージャで削除処理を行なっています。注意すべき点として、 trailingSwipeActionsConfigurationProvider
が Item Identifier ではなく indexPath
を渡してくることです。最小構成を作る過程で分かったように基本的には Diffable DataSource を使った CollectionView の実装では Item Identifier が Item を特定するための手段になるのですが、一部の API では indexPath
を元に処理を書かないといけないことがあります。このような場合には UICollectionViewDiffableDataSource
の itemIdentifier
メソッドに indexPath
を渡して Item Identifier に変換してもらうことができます。今回は、 TodoRepository
に Todo を削除する remoteTodo
メソッドを追加していますが、これが Todo.ID
を引数に取るので indexPath
から変換した id を渡しています。
ちなみに、逆に indexPath
がほしい場面で Item Identifier しか分かっていないこともあり、その場合は UICollectionViewDiffableDataSource
の indexPath
メソッドに Item Identifier を渡します。このように、 indexPath
と Item Identifier の変換は UICollectionViewDiffableDataSource
にお願いすることになります。
TodoRepository
に Todo を削除してもらった後ですが、 Diffable DataSource / CollectionView は表示しているデータの変更を検知したりはしてくれないので、こちらからデータに変更があったことを知らせる必要があります。 Diffable DataSource を使っているため、コードを書くときに差分更新のことを考えずに0からデータを作ってあげればよいので、単に applySnapshot
を呼んでいます。
削除時の動作は以下のようになります。
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
に変換してもらいます。 TodoRepository
の toggleTodo
メソッドを呼んでタップされた Todo の done
プロパティを更新した後に snapshot を apply しますが、これまで使ってきた applySnapshot
とは異なる方法で apply する applySnapshotByReconfiguringItem
メソッドを追加しています。表示する Item Identifier ではなく Item のプロパティが変わった場合は、 NSDiffableDataSourceSnapshot
の reconfigureItems
メソッドにプロパティが変わった Item の Identifier を渡す必要があるためです。手順は、
-
UICollectionViewDataSource
のsnapshot
メソッドを呼んで現在のNSDiffableDataSourceSnapshot
を取得する - 取得した
NSDiffableDataSourceSnapshot
のreconfigureItems
にプロパティが変更された Item の Identifier を渡す -
NSDiffableDataSourceSnapshot
をUICollectionViewDataSource
にapply
する
となります。注意点として、 reconfigureItems
は iOS 15 で追加されたメソッドなので iOS 14 以前では reloadItems
メソッドを使う必要があります。これらの違いとしては、 reloadItems
は Cell を dequeue し直すのに対して reconfigureItems
は既存の Cell をそのまま使うのでよりパフォーマンスがよいという点が挙げられます。ただし、更新時に異なる Cell を使いたい場合などは reloadItems
を使って Cell を dequeue し直す必要があります。より詳しくは reconfigureItems
のドキュメントを参照ください。
タップ時の動作は以下のようになります。
セクションヘッダを追加する
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
に伝える必要があるので、 UICollectionLayoutListConfiguration
の headerMode
を .supplementary
に設定します。デフォルトは .none
で、ヘッダを表示しないということを意味します。今回は Section が1つしかないですが、複数 Section があって Section によってヘッダを出したり出さなかったりしたい場合は indexPath
から Section を取得して headerMode
の設定を分岐させることも可能です。
DataSource 側でやることとして、 UICollectionView.SupplementaryRegistration<Supplementary>
を生成し、それをもとに UICollectionViewDiffableDataSource
の supplementaryViewProvider
を設定しています。この流れば Item を表示する Cell に対して CellRegistration
と cellProvider
を設定したのとまったく同じになっています。
SupplementaryRegistration
の型パラメータには UICollectionReusableView
のサブクラスを指定しますが、今回は Cell と同じ UICollectionViewListCell
を使っています。 Header に Cell と名のつく View を使うのはやや変な感じがしますが、 UICollectionViewListCell
はこのように Cell 以外に対しても使うことができます。これにより SupplementaryRegistration
に渡すクロージャの第一引数の view
は UICollectionViewListCell
になるので、 Cell と同じように defaultContentConfiguration
を使うことでそれらしき見た目にしてくれます。ここでは使っていませんが、 accessories
も設定できて便利です。
supplementaryViewProvider
では、 SupplementaryRegistration
を使って dequeueConfiguredReusableSupplementary
を呼ぶだけです。この流れは Cell の追加と同様です。
以上でヘッダが表示されます。
今回はヘッダのみ追加しましたが、同じような考え方でフッタも追加することができます。
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()
+ }
+ )
+ }
これまで TodoRepository
の todoIDs
はすべての Todo の ID を返していましたが、今回追加した showsOnlyUndoneTodos
プロパティが true
のときは Done になっていない Todo の id のみを返すようにします。
何らかの方法で showsOnlyUndoneTodos
を変更できるようにしたいので、 NavigationBar にボタンを追加して、タップ時に値をトグルするようにします。これによって todoIDs
が変化するので、その後に applySnapshot
を呼んで Snapshot を作り直すことで表示が変化します。 これだけで Diffable DataSource が差分を計算し、アニメーションをつけてくれます。
まとめ
- CollectionView / DataSource を特徴付ける型として Section と Item があり、 Item は自身を特定できる型である Item Identifier を持つ
- DataSource におけるデータ反映の流れを以下のように作る
-
CellRegistration
に Item を Cell の表示に反映する処理を書く -
UICollectionViewDiffableDataSource
のcellProvider
に Item Identifier が渡されてくるので、そこから Item を取得し、 その Item とCellRegistration
を使って Cell を dequeue する処理を書く
-
- 実際のデータ反映をするために
NSDiffableDataSourceSnapshot
を生成し、 Section と Item Identifier を追加してUICollectionViewDiffableDataSource
にapply
する- データを更新する際にも、初回の表示と同じように
NSDiffableDataSourceSnapshot
を新しいデータを使って組み立ててapply
すれば OK
- データを更新する際にも、初回の表示と同じように
- Item Identifier ではなく特定の Item のプロパティの更新を表示に反映したい場合は
UICollectionViewDiffableDataSource
から現在のNSDiffableDataSourceSnapshot
を取得し、reconfigureItems
メソッドに更新したい Item の Identifier を渡して呼んでからapply
する - ヘッダやフッタの追加も Cell と同じ流れで行う。
SupplementaryRegistration
を生成し、それをUICollectionViewDiffableDataSource
のsupplementaryViewProvider
の中で使う
参考
- Advances in diffable data sources - WWDC 20
- Lists in UICollectionView - WWDC 20
- Make blazing fast lists and collection views - WWDC 21
- Updating Collection Views Using Diffable Data Sources - Apple Sample Code
- Implementing Modern Collection Views - Apple Sample Code
- How to add custom swipe actions to a UICollectionViewListCell? - Donny Wals
- Table and Collection View Cells Reload Improvements in iOS 15 - SWIFT SENPAI