最近は自分向けのPDFリーダーアプリを開発しています。
このアプリではSwiftDataを使って読書履歴をモデル化して管理しているのですが、これを踏まえてちょっと自分のメモがてらにTODOアプリを例にしたメモを残しておきます。
Swiftを触り始めて3日目ぐらいなので、書き間違えや嘘などがあるかもしれません。
その場合はコメントなどで教えてもらえると助かります。
TL;DR
- SwiftDataの
@Query
で動的な条件は付与できない - Computed Propertiesを使って
@Query
で定義したフィルターを利用する
基本的な使い方
まず今回のサンプルとなるタスクを一覧表示するViewと、Modelを定義します。
以下のような仕様で作成しておきます。
- Taskモデル
- nameをプロパティとして持つ
- TodoView
- Taskを一覧表示する
- 画面上部にはキーワード検索のためのTextFieldを設定
import SwiftUI import SwiftData @Model final class Task { var name: String init(name: String) { self.name = name } } struct TodoView: View { @Environment(\.modelContext) private var context @State private var keyword: String = "" @Query(sort: \Task.name) private var tasks: [Task] var body: some View { VStack { HStack { TextField("検索...", text: $keyword) .padding() .background(primaryColor) .cornerRadius(5) } .buttonStyle(PlainButtonStyle()) .padding(.horizontal) List(tasks) { task in Text(task.name) } } } } // Preview struct TodoView_Previews: PreviewProvider { static var words: [String] = [ "Summery", "Country", "Perk", "Relief", "Virus", "Hunter", "Photocopy", "Liberal", "Sugar", "Practice", ] static var container: some ModelContainer { let configuration = ModelConfiguration(isStoredInMemoryOnly: true) let container = try! ModelContainer(for: Task.self, configurations: configuration) // プレビュー用データを作成 words.forEach { word in let task = Task(name: word) container.mainContext.insert(task) } return container } static var previews: some View { TodoView() .modelContainer(container) } }
この段階では、検索用のTextFieldがありますが絞り込むロジックを書いていません。
そのため絞り込むためのロジックを書いていきます。
キーワードでタスクを絞り込む
キーワード検索を絞り込むためにはどういったやり方があるでしょうか?
最初は@Queryのfilter
に絞り込むためのロジックを書けばいけると思っていたのですが、ここではkeyword自体を参照することができませんでした。
// エラー @Query(filter: #Predicate<Task> { $0.name.contains(keyword) }, sort: \Task.name) private var tasks: [Task]
そして小一時間ほど公式ドキュメントを眺めたりしながら試行錯誤した結果、Computed Propertiesなプロパティを定義することで実現することができました。
// 成功 @Query(sort: \Task.name) private var tasks: [Task] private var filteredTasks: [Task] { guard !keyword.isEmpty else { return tasks } return tasks.filter { $0.name.contains(keyword) } }
- キーワードが空文字列の場合は
tasks
をそのまま返す - それ以外の場合、タスク名にキーワードが含まれている文字列を返す
というプロパティを定義することで動的なキーワード検索を比較的楽に実現することができました。
もし同じように悩んでいる方がいれば参考にしてみてください。