Reputation: 2768
I have the following code:
import SwiftUI
import SwiftData
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
var schoolClass: SchoolClass
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
Button("add", action: {
let schoolClassSubject = SchoolClassSubject(schoolClass: schoolClass)
schoolClass.schoolClassSubjects.append(schoolClassSubject)
})
}
.padding()
}
}
@Model
class SchoolClass {
var name: String
var schoolClassSubjects: [SchoolClassSubject] = []
init(name: String) {
self.name = name
}
}
@Model
class SchoolClassSubject {
var schoolClass: SchoolClass
init(schoolClass: SchoolClass) {
print("test")
self.schoolClass = schoolClass
}
}
schoolClass
is already saved in swiftData and passed as a property to ContentView
.
The line let schoolClassSubject = SchoolClassSubject(schoolClass: schoolClass)
breaks with the following exception:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Illegal attempt to establish a relationship 'schoolClass' between objects in different contexts (source = <NSManagedObject: 0x60000214cc30> (entity: SchoolClassSubject; id: 0x60000024ebe0 x-coredata:///SchoolClassSubject/t186670BB-357C-400C-8D6F-178BEB8F4B473; data: { schoolClass = nil; }) , destination = <NSManagedObject: 0x60000214cd70> (entity: SchoolClass; id: 0x600000276740 x-coredata:///SchoolClass/t186670BB-357C-400C-8D6F-178BEB8F4B472; data: { name = test; schoolClassSubjects = ( ); }))' *** First throw call
If I change my Model to this:
@Model
class SchoolClassSubject {
var schoolClass: SchoolClass? = nil
init() {
}
}
And my saving code to this:
let schoolClassSubject = SchoolClassSubject()
modelContext.insert(schoolClassSubject)
schoolClassSubject.schoolClass = schoolClass
schoolClass.schoolClassSubjects.append(schoolClassSubject)
But I don't want to make schoolClass
in schoolClassSubject
optional as it is not in reality. How can I make it not optional and still save it without the given error?
Upvotes: 6
Views: 2420
Reputation: 3
Think it depends on how to keep track of your RelationShips.
If One To Many Relation:
SchoolClass
can have many SchoolClassSubject
'sSchoolClassSubject
can only belong to one SchoolClass
@Model
class SchoolClass {
var name: String
@Relationship(deleteRule: .cascade)
var subjects = [SchoolClassSubject]()
init(name: String) {
self.name = name
}
}
@Model
class SchoolClassSubject {
var subject: String
@Relationship(inverse: \SchoolClass.subjects)
var schoolClass: SchoolClass? // ? meaning of optional
init(subject: String) {
self.subject = subject
}
}
Then in WindowGroup you have to have
struct MyApp: App {
...
var body: some Scene {
WindowGroup {
ContentView(schoolClass: schoolClass)
.modelContainer(for: SchoolClass.self)
}
}
}
In Views
struct ContentView: View {
@Environment(\.modelContext) private var context
let schoolClass: SchoolClass
var body: some View {
Button("Save", action: save)
}
private func save() {
let subject = SchoolClassSubject(subject: "String here..")
// Either
subject.schoolClass = schoolClass
// Or
schoolClass.subjects.append(subject)
}
}
Upvotes: 0
Reputation: 51973
This answer was written using Xcode 15.0 beta 2, in future versions I assume this answer will become irrelevant and we can handle this more easily
Once again, you must use the @Relationship
annotation for the relationships to work properly and once you have that SwiftData will handle them for you.
And because of that you should not include any relationship properties in any init
methods for your model.
So change the init in SchoolClassSubject
to
init() {}
(I don't know if it is a bug that we need an empty init here or if this is just an odd case because there are no other properties)
And then change the code in the button action to
let schoolClassSubject = SchoolClassSubject()
modelContext.insert(schoolClassSubject)
schoolClassSubject.schoolClass = schoolClass
To clarify the above code:
Upvotes: 4