I’ve run into a reproducible issue when using SwiftData with @Model classes that contain non-optional value type properties (struct) with optional properties inside.
After creating a new model, the app crashes with:
Fatal error: Passed nil for a non-optional keypath \MyModel.myStruct
This happens immediately after try? modelContext.save()
is called.
This is my example:
import SwiftData
import SwiftUI
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query(sort: \MyModel.name) private var models: [MyModel]
@State private var selectedModel: MyModel?
var body: some View {
NavigationStack {
List(models) { model in
Button(model.name) {
selectedModel = model
}
}
.navigationTitle("Models")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Add") {
let newModel = MyModel(name: "Test")
modelContext.insert(newModel)
try? modelContext.save()
selectedModel = newModel
}
}
}
.sheet(item: $selectedModel) { model in
Text("Editing \(model.name)")
}
}
}
}
@Model
class MyModel {
var name: String
var myStruct: MyStruct
init(name: String) {
self.name = name
self.myStruct = MyStruct(value: nil)
}
}
struct MyStruct: Codable {
var value: String?
}
@main
struct MinimalReproApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: MyModel.self)
}
}
If I change the value
property in MyStruct
so it's no longer an optional, the crash no longer occurs.
I would prefer a solution that ideally does not involve modifying MyStruct
.
You’re encountering this crash due to how SwiftData internally manages persistence and serialization of model properties—especially with non-optional value types (struct) stored as non-optional properties in a @Model class, which themselves contain optional values.
When you declare:
var myStruct: MyStruct
SwiftData treats myStruct as a non-optional embedded value, meaning it expects every key path within myStruct (i.e., value) to also be non-optional, or at the very least, to not be nil at encoding time.
However, you’re passing:
self.myStruct = MyStruct(value: nil)
Even though myStruct is non-optional, its property value is nil, which causes SwiftData’s encoder to crash when it encounters a nil in a non-optional path (as far as the serialization logic is concerned). This is a known current limitation or bug in SwiftData as of iOS 17.
The error you're seeing:
Fatal error: Passed nil for a non-optional keypath \MyModel.myStruct
…indicates that SwiftData’s encoder is incorrectly assuming that because myStruct is non-optional, all of its fields are also non-optional, and it cannot handle nil in a nested optional during encoding.
You have a couple options:
First, you can make myStruct optional — this doesn't change the implementation of MyStruct as you requested:
@Model
class MyModel {
var name: String
var myStruct: MyStruct?
init(name: String) {
self.name = name
self.myStruct = MyStruct(value: nil)
}
}
This is currently the only reliable way to use structs with optional members in SwiftData.
Alternatively, you could provide a non-optional value at init-time (if you really want myStruct non-optional)
init(name: String) {
self.name = name
self.myStruct = MyStruct(value: "")
}
But this changes the semantics of your data—"" is not the same as nil.
Hope that helps, or gets you closer to the answer you're looking for!