MENU

【Swift入門】SwiftDataの@Queryとは?使い方を初心者向けにわかりやすく解説

iOS 17から導入されたSwiftDataは、データの永続化(保存・取得)をより簡単に行えるAppleの新しいフレームワークです。その中でも@Queryは、データベースからデータを取得してSwiftUIビューに表示するための重要な機能です。

この記事では、Swift初心者の方に向けて、@Queryの基本から実践的な使い方まで、わかりやすく解説します。

目次

@Queryとは?

@Queryは、SwiftUIビューでデータベースからデータを自動的に取得するためのプロパティラッパーです。

主な特徴

  1. 自動取得 – データベースから自動的にデータを取得
  2. 自動更新 – データが変更されると、ビューも自動的に更新
  3. シンプルな記述 – 複雑なコードを書かずにデータ取得が可能

対応環境

  • 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 DataSwiftData
プロパティラッパー@FetchRequest@Query
述語の記述NSPredicate(文字列)#Predicate(型安全)
ソート記述NSSortDescriptorKeyPath
型安全性低い高い
コンパイル時チェックなしあり
学習コスト高い低い

よくある質問とトラブルシューティング

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をマスターすれば、データ永続化を含むアプリ開発がより簡単になります。ぜひ実際のプロジェクトで試してみてください!

参考リンク

プログラミングの独学におすすめ
プログラミング言語の人気オンラインコース
独学でプログラミングを学習している方で、エラーなどが発生して効率よく勉強ができないと悩む方は多いはず。Udemyは、プロの講師が動画で実際のプログラムを動かしながら教えてくれるオンライン講座です。講座の価格は、セール期間中には専門書籍を1冊買うよりも安く済むことが多いです。新しく学びたいプログラミング言語がある方は、ぜひUdemyでオンライン講座を探してみてください。
目次