Skip to main content

How to use @Query to read SwiftData objects from SwiftUI

About 2 minSwiftArticle(s)bloghackingwithswift.comcrashcourseswiftswiftdataxcodeappstore

How to use @Query to read SwiftData objects from SwiftUI 관련

SwiftData by Example

Back to Home

How to use @Query to read SwiftData objects from SwiftUI | SwiftData by Example

How to use @Query to read SwiftData objects from SwiftUI

Updated for Xcode 15

SwiftData provides the @Query macro for querying model objects from a SwiftUI view, optionally providing a sort order, a filter predicate, and either a custom animation or a custom transaction to handle changing results smoothly. Even better, @Query automatically stays up to date every time your data changes, and will reinvoke your SwiftUI view so it stays in sync.

As an example, we could define two related models for a Movie and Director, like this:

@Model
class Director {
    var name: String
    var movies: [Movie]

    init(name: String, movies: [Movie]) {
        self.name = name
        self.movies = movies
    }
}

@Model
class Movie {
    var title: String
    var director: Director

    init(title: String, director: Director) {
        self.title = title
        self.director = director
    }
}

And now we could display a list of all movies, sorted by their title, like this:

struct AuthorsView: View {
    @Query(sort: \Movie.title) var movies: [Movie]

    var body: some View {
        NavigationStack {
            List(movies) { movie in
                Text(movie.title)
                Text("Director: \(movie.director.name)")
            }
            .navigationTitle("Movie Time")
        }
    }
}

As you can see, SwiftData automatically provides us with the director names property, even though that's provided through a relationship. SwiftData loads these relationships lazily – if there's a relationship you don't use, it won't be fetched.

::: important:

When you use @Query to load SwiftData objects in your view, that query is run immediately when your view is displayed. This means you should be careful to avoid loading large amounts of data, because it might cause your user interface to freeze temporarily.

:::

There are lots of ways of customizing @Query, such as providing a filter using #Predicate. For example, we might write a filter to show only movies by James Cameron:

@Query(filter: #Predicate<Movie> { movie in
    movie.director.name == "James Cameron"
}, sort: \Movie.title) var movies: [Movie]

You can also get more fine-grained control over the sort order, either by asking for it to be reversed:

@Query(sort: \Movie.title, order: .reverse) var movies: [Movie]

Or by providing an array of sort descriptors, where they get applied in order:

@Query(sort: [SortDescriptor(\Movie.title), SortDescriptor(\Movie.releaseYear, order: .reverse)]) var movies: [Movie]

That sorts movies alphabetically by their title, but if any movies have the same name they'll be sorted by their release year descending.

Tips

Your views can have as many @Query properties as you need, although if there's more than three I'd start to wonder if there were a more efficient approach.

For more advanced purposes, you can provide a custom FetchDescriptor that you've configured with extra options such as a fetch limit or offset. This takes a little thinking because some fetch descriptor options are available only after the descriptor is initialized.

I've found the easiest way to do this is using a static property for the descriptor, so I can reference it freely in the query. For example, if I wanted to show the 10 most recent movies I'd use a reverse release year sort plus a fetch limit of 10, like this:

static var descriptor: FetchDescriptor<Movie> {
    var descriptor = FetchDescriptor<Movie>(sortBy: [SortDescriptor(\.releaseYear, order: .reverse)])
    descriptor.fetchLimit = 10
    return descriptor
}

@Query(descriptor) var latestMovies: [Movie]

Important

Regardless of how it's created, the @Query macro works only inside SwiftUI views. Swift won't stop you using it elsewhere, it just won't work!


이찬희 (MarkiiimarK)
Never Stop Learning.