Thanks for being a part of WWDC25!

How did we do? We’d love to know your thoughts on this year’s conference. Take the survey here

SwiftData "Auto Inserts" array into ModelContext

Definitely one of the stranger quirks of SwiftData I've come across.

I have a ScriptView that shows Line entities related to a Production, and a TextEnterScriptView that’s presented in a sheet to input text.

I’m noticing that every time I type in the TextEditor within TextEnterScriptView, a new Line shows up in ScriptView — even though I haven’t explicitly inserted it into the modelContext.

I'm quite confused because even though I’m only assigning a new Line to a local @State array in TextEnterScriptView, every keystroke in the TextEditor causes a duplicate Line to appear in ScriptView.

In other words, Why is SwiftData creating new Line entities every time I type in the TextEditor, even though I’m only assigning to a local @State array and not explicitly inserting them into the modelContext?

Here is my minimal reproducible example:

import SwiftData
import SwiftUI

@main
struct testApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .modelContainer(for: Line.self, isAutosaveEnabled: false)
        }
    }
}

struct ContentView: View {
    @Environment(\.modelContext) var modelContext
    @Query(sort: \Production.title) var productions: [Production]
    var body: some View {
        NavigationStack {
            List(productions) { production in
                NavigationLink(value: production) {
                    Text(production.title)
                }
            }
            .navigationDestination(for: Production.self) { production in
                ScriptView(production: production)
            }
            .toolbar {
                Button("Add", systemImage: "plus") {
                    let production = Production(title: "Test \(productions.count + 1)")
                    modelContext.insert(production)
                    do {
                        try modelContext.save()
                    } catch {
                        print(error)
                    }
                }
            }
            .navigationTitle("Productions")
        }
    }
}

struct ScriptView: View {
    @Query private var lines: [Line]
    let production: Production
    @State private var isShowingSheet: Bool = false
    var body: some View {
        List {
            ForEach(lines) { line in
                Text(line.content)
            }
        }
        .toolbar {
            Button("Show Sheet") {
                isShowingSheet.toggle()
            }
        }
        .sheet(isPresented: $isShowingSheet) {
            TextEnterScriptView(production: production)
        }
    }
}

struct TextEnterScriptView: View {
    @Environment(\.dismiss) var dismiss
    @State private var text = ""
    @State private var lines: [Line] = []
    let production: Production
    var body: some View {
        NavigationStack {
            TextEditor(text: $text)
                .onChange(of: text, initial: false) {
                    lines = [Line(content: "test line", production: production)]
                }
                .toolbar {
                    Button("Done") {
                        dismiss()
                    }
                }
        }
    }
}

@Model
class Production {
    @Attribute(.unique) var title: String

    @Relationship(deleteRule: .cascade, inverse: \Line.production)
    var lines: [Line] = []

    init(title: String) {
        self.title = title
    }
}

@Model
class Line {
    var content: String
    var production: Production?

    init(content: String, production: Production?) {
        self.content = content
        self.production = production
    }
}

SwiftData does it automatically for you and it must do it or the autosave functionality wouldn't work.

If you would only save one side of a relationship then when the app is restarted the other side would be nil or it crashes if the property is non-optional.

SwiftData "Auto Inserts" array into ModelContext
 
 
Q