To code along, open up the starter project for this lesson. It will initially not compile, but you’ll resolve that as you fill in the missing parts.
You’ll add the model for a Recipe class, and an associated Ingredient class. Note that classes, not structs, are used with SwiftData, and therefore an explicit initializer is required. Add these classes to their respective files in the starter project. Open Recipe.swift. Add the following:
class Recipe {
var name: String
var summary: String = ""
var instructions: String = ""
var ingredients: [Ingredient]
init(name: String, summary: String = "", instructions: String = "",
ingredients: [Ingredient] = []) {
self.name = name
self.summary = summary
self.instructions = instructions
self.ingredients = ingredients
}
}
Next, open Ingredient.swift. Add the following:
class Ingredient {
var name: String
var amount: String
init(name: String, amount: String) {
self.name = name
self.amount = amount
}
}
If I were using Core Data, I’d have to go into the Schema Editor, add a new entity, and add attributes for the properties. With SwiftData, that’s all done with one addition, @Model. In front of each class definition, add @Model. Don’t forget to import SwiftData at the top of the file. Update Recipe.swift to the following:
import SwiftData
@Model
class Recipe {
var name: String
var summary: String = ""
var instructions: String = ""
var ingredients: [Ingredient]
init(name: String, summary: String = "", instructions: String = "",
ingredients: [Ingredient] = []) {
self.name = name
self.summary = summary
self.instructions = instructions
self.ingredients = ingredients
}
}
Update Ingredient.swift to the following:
import SwiftData
@Model
class Ingredient {
var name: String
var amount: String
init(name: String, amount: String) {
self.name = name
self.amount = amount
}
}
That’s it! The Recipe and Ingredient classes are now valid models for use in SwiftData.
The @Model macro sets up a perfectly valid model, but you can also make customizations. For example, to ensure the Recipe’s name is unique, add an @Attribute macro to that property, and pass in the .unique argument:
@Model
class Recipe {
@Attribute(.unique) var name: String
var summary: String = ""
var instructions: String = ""
var ingredients: [Ingredient]
init(name: String, summary: String = "", instructions: String = "",
ingredients: [Ingredient] = []) {
self.name = name
self.summary = summary
self.instructions = instructions
self.ingredients = ingredients
}
}
You can even define deletion rules for the relationships using the @Relationship macro. Add a @Relationship macro with the deleteRule signature, and pass in .cascade.
@Model
class Recipe {
@Attribute(.unique) var name: String
var summary: String = ""
var instructions: String = ""
@Relationship(deleteRule: .cascade)
var ingredients: [Ingredient]
init(name: String, summary: String = "", instructions: String = "",
ingredients: [Ingredient] = []) {
self.name = name
self.summary = summary
self.instructions = instructions
self.ingredients = ingredients
}
}
The choice of .cascade here ensures that when the Recipe is deleted, the deletion cascades down to the ingredients that have been stored for this entry.
Gone are the days of the Persistence.swift file for initializing the persistence stack for your app. SwiftData has a new modifier that lets you define exactly which types you want to consider part of your model. In the AppMain file, add the .modelContainer modifier to the WindowGroup, and pass in the array of @Models you have made so far.
import Foundation
import SwiftData
@main
struct AppMain: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: [Recipe.self, Ingredient.self])
}
}
The modelContainer(for:) modifier takes an array of types you want your model to track, in this case your Recipe and Ingredient models. That’s it! Your app is now aware of your models and ready to go.
Before moving on, add some sample data to your app so you can accurately test in the Preview canvas. In the SampleData.swift file, add the following code:
import Foundation
import SwiftData
@MainActor
class SampleData {
static let shared = SampleData()
let modelContainer: ModelContainer
var context: ModelContext {
modelContainer.mainContext
}
private init() {
let schema = Schema([
Recipe.self, Ingredient.self
])
let modelConfiguration = ModelConfiguration(schema: schema,
isStoredInMemoryOnly: true)
do {
modelContainer = try ModelContainer(for: schema,
configurations: [modelConfiguration])
insertSampleData()
try context.save()
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}
private func insertSampleData() {
for recipe in Recipe.sampleData {
context.insert(recipe)
}
}
}
This class creates an in memory model container that can be populated at creation time with sample data from the Recipe class, and inserted into the #Preview blocks you’ll create in the next section. Note that the schema contains the 3 models you’ve created so far. The compiler should highlight the Recipe.sampleData property as an error. Fix that by opening up Recipe.swift and adding the following at the end of the class definition:
static let sampleData = [
]
At this point, you can either make up some sample data of your own, but if you are using Xcode 26 on a macOS 26 (Tahoe) beta or above, you can ask ChatGPT to give you some sample data. Try it out and see what you get. Place the cursor in between the brackets and press Control-Option-Space. After thinking on it, Xcode should provide you some suggestions, like this one:
Recipe(
name: "Tomato Basil Soup",
summary: "Creamy and comforting tomato soup with fresh basil",
instructions: "Cook onions and garlic in olive oil until softened.
Add canned tomatoes, vegetable broth, and basil. Simmer for
20 minutes. Blend until smooth, then stir in cream. Warm through
and serve.",
ingredients: [
Ingredient(name: "Onion", amount: "1 medium, chopped"),
Ingredient(name: "Garlic", amount: "3 cloves, minced"),
Ingredient(name: "Canned tomatoes", amount: "28 oz"),
Ingredient(name: "Vegetable broth", amount: "2 cups"),
Ingredient(name: "Fresh basil", amount: "1/4 cup, chopped"),
Ingredient(name: "Heavy cream", amount: "1/2 cup"),
Ingredient(name: "Olive oil", amount: "2 tbsp")
],
)
You can then ask ChatGPT to add some more sample data if needed by using the Coding Assistant button in the upper left hand corner of Xcode.
In the next segment, you’ll get a quick refresher on how queries and keeping your user interface up to date are easy with SwiftData.
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 of the basics of SwiftData to get you ready for learning about the changes in the 2026 OSes.
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.