SwiftUI × CoreData でセクション分けされたリストを作る
やりたいこと
特定の要素でグルーピングしてセクション付きリストを作成したい。
つかうもの
SectionedFetchRequest
条件
iOS 15.0+
[1]
サンプルstruct ContentView: View {
@SectionedFetchRequest(
// グルーピングに利用する要素を指定
sectionIdentifier: \Item.category,
// ソートの指定
sortDescriptors: [NSSortDescriptor(keyPath: \Item.category, ascending: true),
NSSortDescriptor(keyPath: \Item.name, ascending: true)],
animation: .default)
private var sections: SectionedFetchResults<String, Item>
var body: some View {
List {
ForEach(sections) { section in
Section(content: {
ForEach(section) { item in
Text(item.name) // りんご
}
}, header: {
// `section.id` で指定した要素(カテゴリ)を取得可能
Text(section.id) // フルーツ
})
}
}
}
}
@objc(Item)
public class Item: NSManagedObject {}
extension Item: Identifiable {
@NSManaged public var category: String
@NSManaged public var name: String
@NSManaged public var price: NSDecimalNumber
}
[2]
注意点sortDescriptors
で name
のみを指定した場合、グルーピングが上手く働かないことがあります。
そのような場合はグルーピングしたい要素( category
)でのソートを優先するように明示することで解決します。
この点についてはドキュメントにも記載されています。
Be sure that you choose sorting and sectioning that work together to avoid discontiguous sections.
推測となりますが、データの取得およびレンダリングのタイミングによる問題だと思われます。
例えば、名前でソートして1000件のデータを取得するようなケースで、1件目と1000件目にカテゴリ「ドリンク」のデータが存在するとします。この状態でドリンクのセクションを表示する場合、1~1000の全てのデータの同時取得およびグルーピングが必要となりますが現実的には難しいかと思われます。
実際は遅延取得が行われ、1件目と1000件目ではグルーピングや画面表示のタイミングがズレてしまう形となり想定する動きとなりません。このような問題を防ぐためにあらかじめカテゴリでソートしておけば、連続したデータとして取得できタイミングも揃えることができるという話だと思います。
まとめ
これまでは取得した一覧データをDictionaryの init(grouping:by:)
などを使ってグルーピングする前処理が必要でしたが、iOS15からはSwiftUIが勝手にやってくれるのでスッキリ書くことができるようになりました。
-
説明のため簡略化したコードとなります ↩︎
Discussion