検索条件を動的に変更する方法を解説します。
【SwiftUI】Core Dataの使い方:検索編(1/3)の続編です。
環境
この記事の情報は次のバージョンで動作確認しています。
【Swift】5.3.2
【iOS】14.4
【macOS】Big Sur バージョン 11.1
はじめに
前記事の【SwiftUI】Core Dataの使い方:検索編(1/3)では、次のようなプロパティラッパー(@FetchRequest)を使った検索方法を紹介しました。
1 2 3 4 5 6 7 |
@FetchRequest( entity: Student.entity(), sortDescriptors: [], predicate: NSPredicate(format: "nameOfClass == %@", "A") ) private var students: FetchedResults<Student> |
抽出条件の"A"の部分を、他のプロパティを参照するように変更すれば、検索条件を動的に変えられそうです。
1 2 3 4 5 6 7 8 9 |
@FetchRequest( entity: Student.entity(), sortDescriptors: [], predicate: NSPredicate(format: "nameOfClass == %@", nameOfClass) ) private var students: FetchedResults<Student> var nameOfClass = "A" |
しかし実際に試してみると、次のようなエラーが発生します。
Cannot use instance member 'nameOfClass' within property initializer; property initializers run before 'self' is available
構造体のプロパティとして作成される @FetchRequest は、Swiftの制限により他のプロパティを参照する事ができないのです。
解決策
この記事では、検索を伴う機能を別のViewに切り分けて、そこに検索キーを渡す方法を紹介します。
切り分けたViewでは、渡された検索キーを使ってイニシャライザでFetchRequestを動的に生成可能となります。
実現したい機能
生徒一覧の上部にPickerでクラスの選択ボタンを配置し、選択したクラスの生徒一覧が表示されるようにします。
呼び出し側(ContentView)の構成
前回の記事で作成したContentViewを次のように変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import SwiftUI struct ContentView: View { @Environment(\.managedObjectContext) private var context let classes = ["A", "B"] @State private var selectionClass = 0 var body: some View { VStack { /// クラスの選択 Picker(selection: $selectionClass, label: Text("クラス名")) { ForEach( 0 ..< classes.count) { index in Text(classes[index]) } } .pickerStyle(SegmentedPickerStyle()) /// 生徒一覧表示View StudentsList(nameOfClass: classes[selectionClass]) } .onAppear { /// Listビュー表示時に初期データ登録処理を実行 registSampleData(context: context) } } } |
VStackで画面の上部にクラスを選択するPicker部品を、下部には生徒一覧を表示するカスタムView(StudentsList)を配置します。
カスタムViewの引数(nameOfClass)には、選択したクラス名を渡します。
1 2 3 4 |
/// 生徒一覧表示View StudentsList(nameOfClass: classes[selectionClass]) |
生徒一覧表示View
次のようなカスタムView(StudentsList.swift)をプロジェクトに新規追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
import SwiftUI struct StudentsList: View { /// ①FetchRequestの保存用 var fetchRequest: FetchRequest<Student> /// ②FetchRequestの生成 init(nameOfClass: String) { fetchRequest = FetchRequest<Student>( entity: Student.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Student.sid, ascending: true)], predicate: NSPredicate(format: "nameOfClass == %@", nameOfClass), animation: .default ) } var body: some View { List { ForEach(fetchRequest.wrappedValue, id: \.self) { student in Section(header: HStack { Text("\(student.sid!)") Text("\(student.name!)") }){ VStack(alignment: .leading) { Text("生年月日:\(student.birthday!, style: Text.DateStyle.date)") Text("欠席日数:\(student.absentDays)") Text("クラス:\(student.nameOfClass!)") Text("部活:\(student.club!)") } } } } } } |
FetchRequest保存用プロパティ
@FetchRequestは使用せず、代わりにカスタムFetchRequestを保存する次のようなプロパティを宣言します。
1 2 3 4 |
/// ①FetchRequestの保存用 var fetchRequest: FetchRequest<Student> |
FetchRequestの生成
イニシャライザの引数で受け取った検索キーを使って、FetchRequestを生成します。
1 2 3 4 5 6 7 8 9 10 11 |
/// ②FetchRequestの生成 init(nameOfClass: String) { fetchRequest = FetchRequest<Student>( entity: Student.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Student.sid, ascending: true)], predicate: NSPredicate(format: "nameOfClass == %@", nameOfClass), animation: .default ) } |
検索結果の取得
検索結果を取得する場合は、次のようにfetchRequestのwrappedValueを使ってデータを引き出す必要があります。
1 2 3 |
ForEach(fetchRequest.wrappedValue, id: \.self) { student in |
wrappedValueを使うのが嫌ならば、次のように単純な計算プロパティを作ってもよいでしょう。
1 2 3 |
var students: FetchedResults<Students> { fetchRequest.wrappedValue } |