iOS 17から導入されたSwiftDataは、データの永続化(保存・取得)をより簡単に行えるAppleの新しいフレームワークです。その中でも@Query
は、データベースからデータを取得してSwiftUIビューに表示するための重要な機能です。
この記事では、Swift初心者の方に向けて、@Query
の基本から実践的な使い方まで、わかりやすく解説します。
@Queryとは?
@Query
は、SwiftUIビューでデータベースからデータを自動的に取得するためのプロパティラッパーです。
主な特徴
- 自動取得 – データベースから自動的にデータを取得
- 自動更新 – データが変更されると、ビューも自動的に更新
- シンプルな記述 – 複雑なコードを書かずにデータ取得が可能
対応環境
- iOS 17.0以降
- macOS 14.0以降
- watchOS 10.0以降
- tvOS 17.0以降
SwiftDataとは?
@Query
を理解するには、まずSwiftDataについて知る必要があります。
SwiftDataは、iOS 17で導入されたApple純正のデータ永続化フレームワークで、Core Dataの後継として位置づけられています。従来のCore Dataよりもシンプルで、Swiftの言語機能を最大限に活用した設計になっています。
@Queryの基本的な使い方
ステップ1: モデルを定義する
まず、保存したいデータの構造を@Model
マクロを使って定義します。
import SwiftData
@Model
class Book {
var title: String
var author: String
var publishYear: Int
var pageCount: Int
init(title: String, author: String, publishYear: Int, pageCount: Int) {
self.title = title
self.author = author
self.publishYear = publishYear
self.pageCount = pageCount
}
}
ステップ2: @Queryでデータを取得する
SwiftUIビューで@Query
を使ってデータを取得します。
import SwiftUI
import SwiftData
struct BookListView: View {
@Query var books: [Book]
var body: some View {
NavigationStack {
List(books) { book in
VStack(alignment: .leading) {
Text(book.title)
.font(.headline)
Text(book.author)
.font(.subheadline)
.foregroundColor(.gray)
}
}
.navigationTitle("書籍一覧")
}
}
}
ポイント:
@Query var books: [Book]
と書くだけで、全ての本のデータが自動的に取得される- データが追加・削除・更新されると、ビューも自動的に更新される
ステップ3: アプリでSwiftDataを設定する
import SwiftUI
import SwiftData
@main
struct BookApp: App {
var body: some Scene {
WindowGroup {
BookListView()
}
.modelContainer(for: Book.self)
}
}
@Queryの高度な使い方
1. ソート(並び替え)
データを特定の順序で取得したい場合、sort
パラメータを使います。
// タイトルの昇順(A→Z)でソート
@Query(sort: \Book.title)
var booksSortedByTitle: [Book]
// 出版年の降順(新しい→古い)でソート
@Query(sort: \Book.publishYear, order: .reverse)
var booksSortedByYear: [Book]
// 複数のソート条件を指定
@Query(sort: [
SortDescriptor(\Book.author),
SortDescriptor(\Book.publishYear, order: .reverse)
])
var booksSortedByAuthorAndYear: [Book]
2. フィルタリング(絞り込み)
特定の条件に合うデータだけを取得したい場合、filter
パラメータを使います。
// 2020年以降に出版された本だけを取得
@Query(filter: #Predicate<Book> { book in
book.publishYear >= 2020
})
var recentBooks: [Book]
// 特定の著者の本だけを取得
@Query(filter: #Predicate<Book> { book in
book.author == "夏目漱石"
})
var booksByNatsume: [Book]
// 複数の条件を組み合わせる
@Query(filter: #Predicate<Book> { book in
book.publishYear >= 2020 && book.pageCount > 300
})
var recentLongBooks: [Book]
3. ソートとフィルタリングの組み合わせ
// 2020年以降の本を、タイトルの昇順で取得
@Query(
filter: #Predicate<Book> { book in
book.publishYear >= 2020
},
sort: \Book.title
)
var filteredAndSortedBooks: [Book]
4. 動的なフィルタリング
ユーザーの入力に応じてフィルタを変更したい場合は、イニシャライザを使います。
struct BookListView: View {
@Query var books: [Book]
init(searchText: String) {
let predicate = #Predicate<Book> { book in
searchText.isEmpty || book.title.contains(searchText)
}
_books = Query(filter: predicate, sort: \Book.title)
}
var body: some View {
List(books) { book in
Text(book.title)
}
}
}
実践的な使用例
例1: 検索機能付きの本のリスト
struct BookSearchView: View {
@State private var searchText = ""
@Query var books: [Book]
init() {
_books = Query(sort: \Book.title)
}
var filteredBooks: [Book] {
if searchText.isEmpty {
return books
}
return books.filter { $0.title.contains(searchText) }
}
var body: some View {
NavigationStack {
List(filteredBooks) { book in
VStack(alignment: .leading) {
Text(book.title)
.font(.headline)
Text("\(book.author) - \(book.publishYear)年")
.font(.caption)
}
}
.searchable(text: $searchText, prompt: "本を検索")
.navigationTitle("書籍検索")
}
}
}
例2: カテゴリー別の表示
@Model
class Book {
var title: String
var author: String
var category: String
var publishYear: Int
init(title: String, author: String, category: String, publishYear: Int) {
self.title = title
self.author = author
self.category = category
self.publishYear = publishYear
}
}
struct BooksByCategoryView: View {
let category: String
@Query var books: [Book]
init(category: String) {
self.category = category
let predicate = #Predicate<Book> { book in
book.category == category
}
_books = Query(
filter: predicate,
sort: \Book.publishYear,
order: .reverse
)
}
var body: some View {
List(books) { book in
Text(book.title)
}
.navigationTitle("\(category)の本")
}
}
例3: 統計情報の表示
struct BookStatisticsView: View {
@Query var allBooks: [Book]
@Query(filter: #Predicate<Book> { $0.publishYear >= 2020 })
var recentBooks: [Book]
var body: some View {
VStack(spacing: 20) {
Text("総書籍数: \(allBooks.count)冊")
.font(.title2)
Text("最近の書籍: \(recentBooks.count)冊")
.font(.title3)
if let averagePages = calculateAveragePages() {
Text("平均ページ数: \(averagePages)ページ")
.font(.title3)
}
}
.padding()
}
func calculateAveragePages() -> Int? {
guard !allBooks.isEmpty else { return nil }
let total = allBooks.reduce(0) { $0 + $1.pageCount }
return total / allBooks.count
}
}
Core Dataの@FetchRequestとの比較
従来のCore Dataを使っていた方向けに、違いを比較します。
Core Data(@FetchRequest)
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Book.title, ascending: true)],
predicate: NSPredicate(format: "publishYear >= %d", 2020)
)
var books: FetchedResults<Book>
SwiftData(@Query)
@Query(
filter: #Predicate<Book> { $0.publishYear >= 2020 },
sort: \Book.title
)
var books: [Book]
主な違い
項目 | Core Data | SwiftData |
---|---|---|
プロパティラッパー | @FetchRequest | @Query |
述語の記述 | NSPredicate(文字列) | #Predicate(型安全) |
ソート記述 | NSSortDescriptor | KeyPath |
型安全性 | 低い | 高い |
コンパイル時チェック | なし | あり |
学習コスト | 高い | 低い |
よくある質問とトラブルシューティング
Q1: @Queryが動作しない
A: .modelContainer(for: YourModel.self)
を忘れていないか確認してください。
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: Book.self) // これが必要!
}
}
Q2: データが更新されない
A: @Model
マクロがモデルクラスに付いているか確認してください。
@Model // これが必要!
class Book {
// ...
}
Q3: 複雑な条件でフィルタリングしたい
A: #Predicate
内で論理演算子を使って複雑な条件を記述できます。
@Query(filter: #Predicate<Book> { book in
(book.publishYear >= 2020 && book.pageCount > 300) ||
book.author == "村上春樹"
})
var complexFilteredBooks: [Book]
Q4: パフォーマンスが気になる
A: 必要なデータだけを取得するようにフィルタリングを活用しましょう。全てのデータを取得してから絞り込むのではなく、データベースレベルでフィルタリングする方が効率的です。
注意点とベストプラクティス
1. 適切なフィルタリング
大量のデータを扱う場合は、必ず適切なフィルタリングを行いましょう。
// 悪い例:全データを取得してからフィルタ
@Query var allBooks: [Book]
var recentBooks: [Book] {
allBooks.filter { $0.publishYear >= 2020 }
}
// 良い例:データベースレベルでフィルタ
@Query(filter: #Predicate<Book> { $0.publishYear >= 2020 })
var recentBooks: [Book]
2. 適切なソート
データベースレベルでソートすることで、パフォーマンスが向上します。
// 推奨
@Query(sort: \Book.title)
var sortedBooks: [Book]
3. @Queryは複数使える
一つのビューで複数の@Query
を使用することができます。
struct MultiQueryView: View {
@Query(sort: \Book.title) var allBooks: [Book]
@Query(filter: #Predicate<Book> { $0.publishYear >= 2020 }) var recentBooks: [Book]
@Query(sort: \Book.publishYear, order: .reverse) var newestFirst: [Book]
var body: some View {
// ...
}
}
まとめ
SwiftDataの@Query
は、データベースからデータを取得するための強力で使いやすい機能です。
ポイントのおさらい:
@Query
は、SwiftDataでデータを取得するためのプロパティラッパー- データの変更を自動的に検知してビューを更新
- フィルタリングとソートが簡単に記述できる
- Core Dataの
@FetchRequest
よりも型安全で使いやすい - iOS 17以降で使用可能
初心者の方は、まずシンプルな@Query var items: [YourModel]
から始めて、徐々にフィルタリングやソートを追加していくことをおすすめします。
SwiftDataと@Query
をマスターすれば、データ永続化を含むアプリ開発がより簡単になります。ぜひ実際のプロジェクトで試してみてください!