SwiftUIでデータをリスト表示するときに使用するListの使い方と、Listを使うときに注意すべきデータの一意性についてまとめました。
Listの使い方
人物名をリスト表示する例を取り上げます。
実装はこんな感じです。
// [2] private struct Person: Identifiable { let id = UUID() let name: String } private let persons: [Person] = [ Person(name: "Alice"), Person(name: "Bob"), Person(name: "Carol"), Person(name: "Dave") ] struct ListTestView: View { var body: some View { List { // [1] ForEach(persons) { person in Text(person.name) } } } }
[1]
persons
の数だけForEachがイテレートされ、List内にTextが並びます。
[2]
ForEachにはIdentifiableに準拠した型の配列を渡す方法と、後述するKeyPathを使う方法があります。
KeyPathを使う場合はこんな感じの実装になります。
// [1] private struct Person { let id = UUID() let name: String } private let persons: [Person] = [ Person(name: "Alice"), Person(name: "Bob"), Person(name: "Carol"), Person(name: "Dave") ] struct ListTestView: View { var body: some View { List { // [2] ForEach(persons, id: \.id) { person in Text(person.name) } } } } struct ListTestView_Previews: PreviewProvider { static var previews: some View { ListTestView() } }
[1]
先ほどとの違いはIdentifiableが無いところだけです。
[2]
引数id
にKeyPathを指定します。ここではPersonのid
プロパティを指定していますが、Hashableに準拠している型のプロパティであれば何でも指定できます。例えば、Personのname
プロパティはStringで、StringはHashableに準拠しているので、以下のように指定することができます。
ForEach(persons, id: \.name) { person in Text(person.name) }
データの一意性を担保する
ForEachに渡すコレクションのデータは一意性が担保されている必要があります。
SwiftUIはデータが一意に識別可能であることで、コレクションデータの追加・削除に伴うアニメーションを自動で行ってくれるとドキュメントに記載があります。UITableViewやUICollectionViewでは自分でこれらを実装する必要がありました。
Members of a list must be uniquely identifiable from one another. Unique identifiers allow SwiftUI to automatically generate animations for changes in the underlying data, like inserts, deletions, and moves.
https://developer.apple.com/documentation/swiftui/displaying-data-in-lists
さて、一意性を担保するのは実装者の責任です。注意しないとIdentifiableに準拠していたとしても一意性を担保できていないことがあり、データが正しく表示されません。
以下は一意性が担保されていない悪い例です。
// [1] private struct Person: Identifiable { let id: Int let name: String } private let persons: [Person] = [ Person(id: 1, name: "Alice"), Person(id: 2, name: "Bob"), Person(id: 1, name: "Carol"), // [2] Person(id: 3, name: "Dave") ] struct ListTestView: View { var body: some View { List { ForEach(persons) { person in Text(person.name) } } } } struct ListTestView_Previews: PreviewProvider { static var previews: some View { ListTestView() } }
[1]
PersonはIdentifiableに準拠しています。一意なインスタンスであることを示すid
プロパティはInt型で定義されています。
[2]
id
プロパティの値がname: "Alice"
とname: "Carol"
で同じ1
が指定されていて、重複してしまっています。
この実装だと以下のような表示になります。Carol
が表示されてほしいところがAlice
となっています。
推測ですが、ForEachがid
の値を元にデータをコレクションから抽出するため、3つ目のデータを抽出しようとしたときにid=1
なのでname: "Alice"
のデータが抽出されてしまっているのではないかと思います。
Identifiableに準拠しているからといって一意性が担保されているわけではないので、id
プロパティの型および値の設計には注意が必要です。
まとめ
Listの使い方について簡単にご紹介しました。Listにコレクションデータを渡すときは、Identifiableに準拠した型を渡す方法と、KeyPathを使う方法があることを説明しました。
また、データの一意性に注意を払う必要があることについて、具体的な悪い例を取り上げて説明しました。
UIKit時代と比べてリスト表示の実装が格段にシンプルになりましたが、Identifiableなど新しい知識も必要となっていますね。