Performing Queries
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.