SwiftData Basics

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

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
Download course materials from Github
Previous: Introduction Next: Performing Queries