SwiftData custom migration crash

Starting point

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.

Where I need to go

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
    }
}

The migration plan

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]
    }
}

The ModelContainer

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)
        )
        
        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)")
        }
    }
}

The 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.

Notes

  • 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.
  • iCloud is enabled
  • I thought that the context used in the custom migration blocks is not the "right" one that I use when I create my container
  • If I use the lightweight migration, everything seems to work fine, but I have to manually do the association between the DayPlan and the CDShift objects

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

I want to add an important detail, this happens only when CloudKit is enabled.

SwiftData custom migration crash
 
 
Q