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?
Selecting any option will automatically load the page