SwiftData custom migration crash when CloudKit enabled

SwiftData custom migration crash when CloudKit enabled

  

I have an app that is in production that has a single entity called CDShift. This is the class:

@Model
final class CDShift {
    var identifier: UUID = UUID()
    var date: Date = Date()
    ...
}

This is how this model is written in the current version. Now, I'm updating the app and I have to do some modifications, that are:

  • add a new entity, called DayPlan
  • add the relationship between DayPlan and CDShift

What I did is this:

enum SchemaV1: VersionedSchema {

    static var versionIdentifier = Schema.Version(1, 0, 0)
    
    static var models: [any PersistentModel.Type] {
        [CDShift.self]
    }

    @Model
    final class CDShift {
        var identifier: UUID = UUID()
        var date: Date = Date()
    }
}

To encapsulate the current CDShift in a version 1 of the schema. Then I created the version 2:

enum SchemaV2: VersionedSchema {

    static var versionIdentifier = Schema.Version(2, 0, 0)
    
    static var models: [any PersistentModel.Type] {
        [CDShift.self, DayPlan.self]
    }

    @Model
    final class DayPlan {
        var identifier: UUID = UUID()
        var date: Date = Date()
        @Relationship(inverse: \CDShift.dayPlan) var shifts: [CDShift]? = []
    }

    @Model
    final class CDShift {
        var identifier: UUID = UUID()
        var date: Date = Date()
        var dayPlan: DayPlan? = nil
    }
}

Finally, I created the migration plan:

enum MigrationPlan: SchemaMigrationPlan {
    static var schemas: [any VersionedSchema.Type] {
        [SchemaV1.self, SchemaV2.self]
    }
    
    static let migrateV1toV2 = MigrationStage.custom(
        fromVersion: SchemaV1.self,
        toVersion: SchemaV2.self) { context in
            // willMigrate, only access to old models
        } didMigrate: { context in
            // didMigrate, only access to new models
            
            let shifts = try context.fetch(FetchDescriptor<SchemaV2.CDShift>())
            
            for shift in shifts {
                let dayPlan = DayPlan(date: shift.date)
                dayPlan.shifts?.append(shift)
                context.insert(dayPlan)
            }
        }


    static var stages: [MigrationStage] {
        print("MigrationPlan | stages called")
        return [migrateV1toV2]
    }
}

Last, but not least, how the model container is created in the App:

struct MyApp: App {

    private let container: ModelContainer

    init() {
        container = ModelContainer.appContainer
    }

    var body: some Scene {
        WindowGroup {
            ...
        }
        .modelContainer(container)
    }
}

This is the extension of ModelContainer:

extension ModelContainer {
    
    static var appContainer: ModelContainer {
        let schema = Schema([
            CDShift.self,
            DayPlan.self
        ])
        let modelConfiguration = ModelConfiguration(
            schema: schema,
            isStoredInMemoryOnly: Ecosystem.current.isPreview,
            groupContainer: .identifier(Ecosystem.current.appGroupIdentifier)
        )
        
        AMLogger.verbose("Group container: \(modelConfiguration.groupContainer)")
        AMLogger.verbose("Group container identifier: \(String(describing: modelConfiguration.groupAppContainerIdentifier))")
        
        do {
//            let container = try ModelContainer(for: schema, configurations: modelConfiguration)
            let container = try ModelContainer(for: schema, migrationPlan: MigrationPlan.self, configurations: modelConfiguration)
            AMLogger.verbose("SwiftData path: \(modelConfiguration.url.path)")
            return container
        } catch (let error) {
            fatalError("Could not create ModelContainer: \(error)")
        }
    }
}

This has always worked perfectly until the migration. It crashes on the fatalError line, this is the error:

Unable to find a configuration named 'default' in the specified managed object model.

Another thing that is strange is that it seems that the version of the store is never updated to 2, but it keeps staying on 1. I tried also using the lightweight migration, no crash, it seems it recognizes the new entity, but the store version is always 1.

Do you have an idea on how to help in this case?

EDIT: Important details is that this happens only if CloudKit is enabled.

Answer

Looks like a similar issue to the one raised at here. Solution there was to enable "Deploy Schema Changes" for the datastore's CloudKit Container in the CloudKit Console.

© 2024 Dagalaxy. All rights reserved.