Data Persistence with SwiftData

Mar 19 2025 · Swift 5.10, iOS 17, ipadOS 17, macOS 15, visionOS 1.2, Xcode 15

Lesson 02: SwiftData & SwiftUI Integration

One to Many Relationships Demo

Episode complete

Play next episode

Next

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

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

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

Unlock now

Making Connections - Relationships

The real power of SwiftData shines when you add relationships between models. Relationships provide for data that can uniquely associated, or data that’s easily shared with multiple records, and also helps eliminate input errors.

import Foundation
import SwiftData

@Model
class BreedModel {
  var name: String
  var dogs: [DogModel]?

  init(name: String) {
    self.name = name
  }
}
@Model
class DogModel {
  //...

  // 1. change breed type to BreedModel
  var breed: BreedModel?

  init(
    // ...

    // 2. change breed type to BreedModel
    breed: BreedModel? = nil,
    // ...
  ) {
    // ...

    self.breed = breed  // no change
    // ...
  }
}

// in the DogModel - optional
@Relationship(inverse: \BreedModel.name)
var breed: BreedModel?

// in the BreedModel
@Relationship
var dogs: [DogModel]?
extension DogModel {
  @MainActor
  static var preview: ModelContainer {
    let container = try! ModelContainer(
      for: DogModel.self,
      configurations: ModelConfiguration(
        isStoredInMemoryOnly: true)
    )

    // breeds
    let labrador = BreedModel(name: "Labrador Retriever")
    let golden = BreedModel(name: "Golden Retriever")
    let bouvier = BreedModel(name: "Bouvier")
    let mixed = BreedModel(name: "Mixed")

    // dogs
    let macDog = DogModel(
      name: "Mac",
      age: 11,
      weight: 90,
      color: "Yellow",
      breed: labrador,
      image: nil
    )
    let sorcha = DogModel(
      name: "Sorcha",
      age: 1,
      weight: 40,
      color: "Yellow",
      breed: golden,
      image: nil
    )
    let violet = DogModel(
      name: "Violet",
      age: 4,
      weight: 85,
      color: "Gray",
      breed: bouvier,
      image: nil
    )
    let kirby = DogModel(
      name: "Kirby",
      age: 11,
      weight: 95,
      color: "Fox Red",
      breed: labrador,
      image: nil
    )
    let priscilla = DogModel(
      name: "Priscilla",
      age: 17,
      weight: 65,
      color: "White",
      breed: mixed,
      image: nil
    )

    container.mainContext.insert(macDog)
    container.mainContext.insert(sorcha)
    container.mainContext.insert(violet)
    container.mainContext.insert(kirby)
    container.mainContext.insert(priscilla)

    return container
  }
}
dog.breed?.name.localizedStandardContains(filterString) ?? false
@State private var breed: BreedModel?
LabeledContent {
//  TextField("", text: $breed)
} label: {
  Text("Breed")
    .foregroundStyle(.secondary)
}
.onAppear {
  name = dog.name
  age = dog.age ?? 0
  weight = dog.weight ?? 0
  color = dog.color ?? ""
  breed = dog.breed
  image = dog.image
}
// #Preview
let dog = DogModel(
  name: "Mac",
  age: 11,
  weight: 90,
  color: "Yellow"
)
HStack {
  Text("age: \(String(describing: dog.age ?? 0))")
  Text("breed: \(String(describing: dog.breed?.name ?? ""))")
}
.font(.footnote)
@Query(sort: \BreedModel.name) private var breeds: [BreedModel]
@Binding var selectedBreed: BreedModel?
#Preview {
  let container = try! ModelContainer(for: DogModel.self)
  let selectedBreed = Binding<BreedModel?>.constant(BreedModel(name: "Labrador Retriever"))
  return BreedPicker(selectedBreed: selectedBreed)
    .modelContainer(container)
}
var body: some View {
  Picker("Breed", selection: $selectedBreed) {
    Text("Select breed").tag(nil as BreedModel?)
    ForEach(breeds) { breed in
      Text(breed.name).tag(Optional(breed))
    }
  }
  .buttonStyle(.bordered)
}
LabeledContent {
  BreedPicker(selectedBreed: $breed)
} label: {
  Text("Breed")
    .foregroundStyle(.secondary)
}

List of Breeds

Next you’ll update the BreedListView view for SwiftData. This is similar to the DogList. Select the BreedListView from the Project Navigator, and import SwiftData at the top.

@Environment(\.modelContext) private var modelContext
@Query(sort: \BreedModel.name) private var breeds: [BreedModel]
#Preview {
  BreedListView()
    .modelContainer(DogModel.preview)
}
ForEach(breeds) { breed in
  Text(breed.name)
}
func breedToDelete(indexSet: IndexSet) {
  for index in indexSet {
    modelContext.delete(breeds[index])
  }
}
.onDelete(perform: breedToDelete)

Add a Breed

You’ll need a way to add breeds to the app. Add a button to navigate the the BreedList. At the top of the EditDogView struct there is a state variable to show the breed list.

@State private var showBreeds = false
HStack {
  BreedPicker(selectedBreed: $breed)
  Button("Edit Breeds") {
    showBreeds = true
  }
  .buttonStyle(.borderedProminent)
}
.sheet(isPresented: $showBreeds) {
  BreedListView()
    .presentationDetents([.large])
}
@Environment(\.modelContext) private var modelContext
let newBreed = BreedModel(name: name)
modelContext.insert(newBreed)
try? modelContext.save()

Walk the Dog

Now that you’ve added SwiftData you can try out adding a new breed. Select the DogListView and start the Canvas Preview. Click to select a dog. Notice that the mock data is loading the breed in the picker. Select a different breed and press Update. Marvel at how the breed has changed.

NavigationLink{
  EditBreedView()
} label: {
  Text(breed.name)
}
import SwiftData
@Bindable var breed: BreedModel
var changed: Bool {
  name != breed.name
}
#Preview {
  let container = try! ModelContainer(for:
      DogModel.self)
  let breed = BreedModel(name: "Labrador")
  return EditBreedView(breed: breed)
    .modelContainer(container)
}
EditBreedView(breed: breed)
.task {
  name = breed.name
}
Button ("Update Breed") {
  if changed {
    breed.name = name
  }
  dismiss()
}

See forum comments
Cinema mode Download course materials from Github
Previous: Sort Filter Demo Next: Many to Many Relationships Demo