I’m having an issue when two of my SwiftData models have a one-to-many relationship and I have them synced via CloudKit.

To be clear, I’ve met all of the requirements to make it iCloud friendly and sync to work. I followed this https://www.hackingwithswift.com/quick-start/swiftdata/how-to-sync-swiftdata-with-icloud, and can confirm I’ve done it correctly because initially I was seeing this crash on startup when I had not:

Thread 1: Fatal error: Could not create ModelContainer: SwiftDataError(_error: SwiftData.SwiftDataError._Error.loadIssueModelContainer)

This is to say, the problem may be iCloud related but it’s not due to a wrong model setup. Speaking of which, these are models:

@Model
class Film {
    var name: String = ""
    var releaseYear: Int = 0
    var director: Director? = nil

    init(name: String, releaseYear: Int, director: Director) {
        self.name = name
        self.releaseYear = releaseYear
        self.director = director
    }
}

@Model
class Director {
    var name: String = ""
    
    @Relationship(deleteRule: .cascade, inverse: \Film.director)
    var films: [Film]? = []

    init(name: String, films: [Film]) {
        self.name = name
        self.films = films
    }
}

I’ve set the delete rule for the relationship between Film and Director to be cascading because you can’t have a film without a director (to be clear, even when set as nullify, it doesn’t make a difference)

And this is the @main App definition:

@main
struct mvpApp: App {
    var sharedModelContainer: ModelContainer = {
        let schema = Schema([
            Film.self,
            Director.self
        ])
        let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)

    do {
        return try ModelContainer(for: schema, configurations: [modelConfiguration])
    } catch {
        fatalError("Could not create ModelContainer: \(error)")
    }
}()

var body: some Scene {
    WindowGroup {
        ContentView()
    }
}

And this is the dummy ContentView:

struct ContentView: View {
    var body: some View {
        EmptyView()
            .onAppear {
                let newDirector = Director(name: "Martin Scorcese", films: [])
                let film = Film(name: "The Wolf of Wall Street", releaseYear: 2019, director: newDirector)
                newDirector.films!.append(film)
            }
    }
}

I create a Director with no films assigned. I then create a Film, and the append it to the Director’s [Film] collection.

The last step however causes a crash consistently:

Crash when I append a Film to a Director’s Film collection

There is a workaround that involves removing this line from the Film init():

self.director = director // comment this out so it’s not set in a Film’s init()

When I do this, I can append the (Director-less) Film to the Director’s [Film] collection.

Am I misunderstanding how these relationships should work in SwiftData/CloudKit? It doesn’t make any sense to me that when two models are paired together that only one of them has a reference to the relationship, and the other has no knowledge of the link.

The above is a minimum reproducible example (and not my actual application). In my application, I basically compromised with the workaround that initially appears to be without consequence, but I have begun to notice crashes happening semi-regularly when deleting models that I suspect must be linked to setting the foundations incorrectly.

UPDATE: Joakim Danielson commented (as well as answered elsewhere) a potential workaround: https://stackoverflow.com/a/77500310/698971.

In short:

When creating two related objects at the same time then you need insert the one you want to assign to into your model context first before connecting the two

So I updated my ContentView to:

struct ContentView: View {
    
    @Environment(\.modelContext) private var modelContext
    
    var body: some View {
        EmptyView()
            .onAppear {
                let newDirector = Director(name: "Martin Scorcese", films: [])
                modelContext.insert(newDirector) /// recommended workaround
                let film = Film(name: "The Wolf of Wall Street", releaseYear: 2019, director: newDirector)
                newDirector.films!.append(film)
            }
    }
}

Obviously ensure the model container is passed into the environment at the App root level:

WindowGroup {
    ContentView()
        .modelContainer(sharedModelContainer)
}
 

This did the trick!!

0

There are 0 best solutions below