← All shorts
65iOS 17+2 min read

@Query, Predicates, and Sorting in SwiftData

@Query keeps SwiftUI views in sync with SwiftData automatically, driven by #Predicate and SortDescriptor.

Fetching Core Data objects into a SwiftUI list means an @FetchRequest with an NSPredicate format string and an NSSortDescriptor — both untyped, both checked only at runtime.

#The old way

import SwiftUI
import CoreData

struct RecipeListView: View {
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Recipe.createdAt, ascending: false)],
        predicate: NSPredicate(format: "servings > %d", 2)
    )
    private var recipes: FetchedResults<Recipe>

    var body: some View {
        List(recipes) { recipe in
            Text(recipe.title)
        }
    }
}

#The new way

import SwiftUI
import SwiftData

struct RecipeListView: View {
    @Query(
        filter: #Predicate<Recipe> { $0.servings > 2 },
        sort: \Recipe.createdAt,
        order: .reverse
    )
    private var recipes: [Recipe]

    var body: some View {
        List(recipes) { recipe in
            Text(recipe.title)
        }
    }
}

#Why it matters

  • #Predicate is compiled and type-checked against the model's real property types — no format strings, no silent typos
  • Sorting takes a key path directly instead of a stringly-typed keyPath: argument
  • @Query re-executes automatically whenever the underlying ModelContext changes, so the list updates without manual refresh logic
  • Composes with SwiftUI's own .searchable and other state-driven view modifiers without extra glue code

#Gotcha

@Query's filter and sort parameters are fixed at declaration time — you can't hand it a variable predicate built from view state directly in the property wrapper. To filter by something dynamic like search text, give the view a custom init that builds the #Predicate and assigns it via _recipes = Query(filter: ..., sort: ...). Also worth knowing: @Query re-runs on every context save, not just ones touching the queried type, so a view watching a large or frequently-written model can end up refetching more than expected — profile before assuming it's free.

swiftdataiosquery

Related shorts