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
This content was released on Dec 10 2025. The official support period is 6-months
from this date.
In this segment, you’ll get a refresher about making queries in SwiftData.
Download course materials from Github
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress,
bookmark, personalise your learner profile and more!
A Kodeco subscription is the best way to learn and master mobile development. Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive catalog of 50+ books and 4,000+ videos.