6

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?

1
  • Any luck on this? Same issue here, I have two models, one of which needs a non-optional (required) property of the other model. Commented Oct 6, 2023 at 1:49

2 Answers 2

4

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:

  • First create an instance of your model object
  • Then insert the object into the model context
  • Lastly assign any relationship properties to your object
  • (save)
6
  • 1
    This only works for me if schoolClass in schoolClassSubject is optional. But i want it to be not optional as stated in the question. Commented Jul 4, 2023 at 5:13
  • I was using the exact same models as you have, nothing is optional. Commented Jul 4, 2023 at 5:28
  • I actually ran into the exact same problem with my own code today and the solution was exactly as stated in this answer. Commented Jul 4, 2023 at 13:43
  • @JoakimDanielson – How are you solving it on the latest beta (7)? As this doesn't seem to be working anymore.
    – Thecafremo
    Commented Aug 28, 2023 at 22:37
  • @Thecafremo Yes, looks like this question. I haven’t had time to look into it more than what is mentioned in the comments Commented Aug 29, 2023 at 5:57
0
Think it depends on how to keep track of your RelationShip.
If One To Many Relation.

SchoolClass kan have many SchoolClassSubject
SchoolClassSubject only belong to one SchoolClass

@Model
class SchoolClass {
    var name: String
     @Relationship(deleteRule: .cascade)
     var schoolClassSubjects =[SchoolClassSubject]() 
    init(name: String) {
        self.name = name
    }
}

@Model
class SchoolClassSubject {
    var schoolClassSubject: String

    @Relationship(inverse: \SchoolClass.schoolClassSubjects)
    var schoolClass: SchoolClass?  // ? meaning of optional 


    init(schoolClassSubject: String) {
        //print("test")
        self.schoolClassSubject = schoolClassSubject
    }
}
// then in WindowGroup you have to have
 .modelContainer(for: SchoolClass .self

//In Views
 @Environment(\.modelContext) private var context
    
    let schoolClass: SchoolClass

// You can do in Views save func
let transaction = SchoolClassSubject(schoolClassSubject: "String here..")
       

transaction.schoolClass = schoolClass

1
  • It might be helpful to break this up into separate snippets so that code that belongs in a single file is grouped together and separated from other helpful lines of code.
    – inder_gt
    Commented Oct 7, 2023 at 0:53

Not the answer you're looking for? Browse other questions tagged or ask your own question.