データベースを検索して、オブジェクトとして取得するfetch処理について解説します。
【SwiftUI】Core Dataの使い方:基本編を先に見ておくことをおすすめします。
環境
この記事の情報は次のバージョンで動作確認しています。
【Swift】5.3.2
【iOS】14.4
【macOS】Big Sur バージョン 11.1
データの準備
検索対象として、次のような初期データのStudentテーブルを用意します。
エンティティの定義は次の通り
次の初期データの登録処理(RegistSampleData.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 38 39 40 |
import CoreData func registSampleData(context: NSManagedObjectContext) { /// Studentテーブル初期値 let studentList = [ ["001", "カピバラ", "2010/04/16", "3", "A", "バスケット"], ["002", "アライグマ", "2011/02/06", "0", "B", "サッカー"], ["003", "カイウサギ", "2010/04/08", "10", "B", ""], ["004", "ハクビシン", "2010/12/21", "7", "A", "吹奏楽"], ["005", "ワオキツネザル", "2010/9/20", "5", "A", "サッカー"] ] /// Studentテーブル全消去 let fetchRequest = NSFetchRequest<NSFetchRequestResult>() fetchRequest.entity = Student.entity() let students = try? context.fetch(fetchRequest) as? [Student] for student in students! { context.delete(student) } let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy/M/d" /// Studentテーブル登録 for student in studentList { let newStudent = Student(context: context) newStudent.sid = student[0] // 生徒番号 newStudent.name = student[1] // 氏名 newStudent.birthday = dateFormatter.date(from: student[2])! // 生年月日 newStudent.absentDays = Int16(student[3])! // 欠席日数 newStudent.nameOfClass = student[4] // クラス名 newStudent.club = student[5] // 所属クラブ } /// コミット try? context.save() } |
検索処理のサンプルコード
テーブルを検索して表示する部分(ContentView.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 38 |
import SwiftUI import CoreData struct ContentView: View { @Environment(\.managedObjectContext) private var context /// データ取得処理 @FetchRequest( entity: Student.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Student.sid, ascending: true)], predicate: nil, animation: .default ) private var students: FetchedResults<Student> var body: some View { List { ForEach(students, 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!)") } } } } .onAppear { /// Listビュー表示時に初期データ登録処理を実行 registSampleData(context: context) } } } |
実行結果
データ取得処理
SwiftUIではデータベースの検索結果とViewを同期する為の大変便利な仕組みとしてプロパティラッパー(@FetchRequest)が用意されています。
@FetchRequestを使ってプロパティを宣言すると、プロパティに検索結果が格納されるとともに、データの変更に応じて検索結果が常に最新に保たれます。
このプロパティを使ってViewを生成すると、データの変更がViewに即時反映される仕組みです。
@FetchRequest(…) の部分がプロパティラッパーの定義、private var students… 以降がプロパティの宣言です。
1 2 3 4 5 6 7 8 |
@FetchRequest( entity: Student.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Student.sid, ascending: true)], predicate: nil, animation: .default ) private var students: FetchedResults<Student> |
@FetchRequestの引数
@FetchRequestに指定可能な引数は次の通りです。
entity
検索対象entityを"エンティティ名.entity()"で指定します。
sortDescriptors
検索結果のソート順をNSSortDescriptorの配列で指定します。
トート順の指定を省略するには空の配列を渡します。
predicate(省略可)
検索結果の抽出条件を指定します。
省略した場合は、nil(抽出条件無し=全て)がデフォルト値として使用されます。
animation(省略可)
フェッチされた結果への変更に使用されるアニメーション効果をAnimation構造体で指定します。
省略した場合は、nil(アニメーション無し)がデフォルト値として使用されます。
Animation構造体については、を参照して下さい。
例のように.defaultを指定すると、標準のアニメーションが適用されます。
ソート順の指定
検索結果のソート順は、NSSortDescriptorクラスを使用して指定します。
引数keyPathで並べ替える属性を、引数ascendingで昇順(true)か降順(false)を指定します。
例えば生徒の名前を昇順にソートするには、次のようにします。
1 2 3 4 5 6 |
@FetchRequest( entity: Student.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Student.name, ascending: true)] ) private var students: FetchedResults<Student> |
ソート順の指定は複数重ねられます。
次は、クラス名+生年月日でソートした例です。
1 2 3 4 5 6 |
sortDescriptors: [ NSSortDescriptor(keyPath: \Student.nameOfClass, ascending: true), NSSortDescriptor(keyPath: \Student.birthday, ascending: true) ] |
ちなみに対象属性は次のようにkeyPathに代わりに、引数keyを使って文字列で属性名を指定可能です。しかし、KeyPathを使う方が、予測入力やBuild時のチェックが機能するのでより安全です。
1 2 3 |
sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)] |
抽出条件の指定
抽出条件は、NSPredicateクラスを使用して指定します。
次はAクラスの生徒のみ抽出する例です。
1 2 3 4 5 6 7 |
@FetchRequest( entity: Student.entity(), sortDescriptors: [], predicate: NSPredicate(format: "nameOfClass == 'A'") ) private var students: FetchedResults<Student> |
データに引用符が含まれていると複雑になるので、代わりに次のような構文を使用するのが一般的です。
%@ は「ここに文字列を挿入する」という意味で、そのデータをインラインではなくパラメータとして提供することができます。
1 2 3 |
predicate: NSPredicate(format: "nameOfClass == %@", "A") |
抽出条件のバリエーション
抽出条件の指定方法を他にもいくつか紹介します。
名前が"カ"から始まる生徒を抽出
1 2 3 |
predicate: NSPredicate(format: "name BEGINSWITH %@", "カ") |
名前が"アライグマ"か"ハクビシン"のいずれかに一致する生徒を抽出
1 2 3 |
predicate: NSPredicate(format: "name IN %@", ["アライグマ", "ハクビシン"]) |
欠席日数が0日の生徒を抽出
※整数を挿入するときは%dを使います。
1 2 3 |
predicate: NSPredicate(format: "absentDays == %d", 0) |
欠席日数が5日以上の生徒を抽出
1 2 3 |
predicate: NSPredicate(format: "absentDays => %d", 5) |
NSPredicateは他にも多くの記述方法がありますが、こちらのサイトが参考になります。