Performing Queries

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Making a struct into a @Model is easy, but what about accessing the data? With a model defined and the modelContainer injected into the environment, you can access your database entries with a @Query! Add a @Query macro to the start of the line that declares the recipes in RecipeListView in ContentView.swift. Don’t forget to import SwiftData at the top.

@Query var recipes: [Recipe]
var body: some View {
    List(recipes) { recipe in
        NavigationLink(recipe.name, destination: RecipeDetailView(recipe: recipe))
    }
}

Just like that, your view is now in sync with your database. @Query is like @State, so as changes to your database take place, the recipes property will get updated, and the view will refresh.

You can even customize the query to handle things like sorting and ordering. Add sort: \Recipe.name, order: .forward arguments to the @Query call:

@Query(sort: \Recipe.name, order: .forward) 
var recipes: [Recipe]

var body: some View {
    List(recipes) { recipe in
        NavigationLink(recipe.name, destination: RecipeDetailView(recipe: recipe))
    }
}

This will sort the recipes by name, in ascending, or forward, order.

To insert and delete data from the datastore in Core Data, you needed access to the store’s context. The same is true for SwiftData. When you set up the .modelContainer earlier, that also set up a default model context and injected it into the environment. This allows all SwiftUI views in the hierarchy to access it via the \.modelContext key path in the environment. Add in the @Environment(\.modelContext) private var modelContext line at the top of the view.

struct RecipeListView: View
{
  @Environment(\.modelContext) private var modelContext // Add this line

Once you have that, you can use context.insert() and context.delete() calls to insert and delete objects from the context. Add two new functions to RecipeListView, one for inserting and another for deletion

private func addRecipe() {
  withAnimation {
    let newRecipe = Recipe(name: "New Recipe")
      modelContext.insert(newRecipe)
  }
}

private func deleteRecipes(offsets: IndexSet) {
  withAnimation {
    for index in offsets {
      modelContext.delete(recipes[index])
    }
  }
}

You can then add an onDelete modifier to handle deletions, and a .toolbar modifier to add edit and add buttons to the view. Update the rest of the body to look like the following:

var body: some View {
  List {
    ForEach(recipes) { recipe in
      NavigationLink(recipe.name, destination: RecipeDetailView(recipe: recipe))
    }
    .onDelete(perform: deleteRecipes)
  }
  .toolbar {
    ToolbarItem(placement: .navigationBarTrailing) {
      EditButton()
    }
    ToolbarItem {
      Button(action: addRecipe) {
        Label("Add Recipe", systemImage: "plus")
      }
    }
  }
}

You’ll see this code in action in the next lesson.

Remember, @Querys are like @State objects, so as your database changes, the interface is notified and gets redrawn if the query returns a different set of values. This includes insertions, deletions, sorting changes, and changes in the query results do to predicates.

See forum comments
Download course materials from Github
Previous: SwiftData Basics Next: Conclusion