My app crashes at startup when trying to create SwiftData ModelContainer after creating a migration state from V1 to V2.
This is the error I receive: Thread 1: Fatal error: Failed to create ModelContainer for Shoe: The operation couldn’t be completed. (SwiftData.SwiftDataError error 1.)
What has changed in my only model from V1 to V2?
I added a new property called defaultRunTypes: [RunType] where RunType is an enum with 5 different cases.
V1 model has only isDefaultShoe: Bool property and I want to extend the functionality to have multiple defaults for different types of run.
Now, here is some code.
This is how I create the container:
typealias Shoe = ShoesSchemaV2.Shoe
final class ShoesStore {
static let container = {
let container: ModelContainer
do {
container = try ModelContainer(for: Shoe.self, migrationPlan: ShoesMigrationPlan.self)
} catch {
fatalError("Failed to create ModelContainer for Shoe: \(error.localizedDescription)")
}
return container
}()
}
This is the migration plan.
enum ShoesMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[ShoesSchemaV1.self, ShoesSchemaV2.self]
}
static var stages: [MigrationStage] {
[migrateV1toV2]
}
static let migrateV1toV2 = MigrationStage.custom(
fromVersion: ShoesSchemaV1.self,
toVersion: ShoesSchemaV2.self,
willMigrate: { context in
print("Migration: willMigrae")
let v1Shoes = try? context.fetch(FetchDescriptor<ShoesSchemaV1.Shoe>())
for v1Shoe in v1Shoes ?? [] {
print("Migration: V1 Shoe: \(v1Shoe.brand) \(v1Shoe.model) - isDefaultShoe = \(v1Shoe.isDefaultShoe)")
let v2Shoe = ShoesSchemaV2.Shoe(
id: v1Shoe.id,
image: v1Shoe.image,
brand: v1Shoe.brand,
model: v1Shoe.model,
nickname: v1Shoe.nickname,
lifespanDistance: v1Shoe.lifespanDistance,
aquisitionDate: v1Shoe.aquisitionDate,
totalDistance: v1Shoe.totalDistance,
totalDuration: v1Shoe.totalDuration,
lastActivityDate: v1Shoe.lastActivityDate,
isRetired: v1Shoe.isRetired,
retireDate: v1Shoe.retireDate,
isDefaultShoe: v1Shoe.isDefaultShoe,
defaultRunTypes: v1Shoe.isDefaultShoe ? [.daily] : [], // this is the newly added property
workouts: v1Shoe.workouts,
personalBests: v1Shoe.personalBests,
totalRuns: v1Shoe.totalRuns
)
print("Migration: V2 shoe created: \(v1Shoe.brand) \(v1Shoe.model) - defaultRunTypes: \(v2Shoe.defaultRunTypes.count)")
context.insert(v2Shoe)
context.delete(v1Shoe)
}
try? context.save()
},
didMigrate: { context in
print("Migration: didMigrate")
let v2Shoes = try? context.fetch(FetchDescriptor<ShoesSchemaV2.Shoe>())
for v2Shoe in v2Shoes ?? [] {
print("Migration: V2 shoe: \(v2Shoe.brand) \(v2Shoe.model) - defaultRunTypes: \(v2Shoe.defaultRunTypes.count)")
}
}
)
}
When migrating, I have the following console output. willMigrate is being executed, but not didMigrate
Migration: willMigrate
Migration: V1 Shoe: Adidas Adizero Boston 12 - isDefaultShoe = true
Migration: V2 shoe created: Adidas Adizero Boston 12 - defaultRunTypes: 1
Migration: V1 Shoe: Nike Pegasus Turbo - isDefaultShoe = false
Migration: V2 shoe created: Nike Pegasus Turbo - defaultRunTypes: 0
Migration: V1 Shoe: Nike Zoomfly 5 - isDefaultShoe = false
Migration: V2 shoe created: Nike Zoomfly 5 - defaultRunTypes: 0
Migration: V1 Shoe: Nike Vaporfly 3 - isDefaultShoe = false
Migration: V2 shoe created: Nike Vaporfly 3 - defaultRunTypes: 0
Migration: V1 Shoe: Hoka Speedgoat 6 - isDefaultShoe = false
Migration: V2 shoe created: Hoka Speedgoat 6 - defaultRunTypes: 0
ShoeHealth/ShoesStore.swift:21: Fatal error: Failed to create ModelContainer for Shoe: The operation couldn’t be completed. (SwiftData.SwiftDataError error 1.)
I don't know what is going on here since is the first migration that I am doing. Is the way I am creating the ModelContainer a problem (being static - I am doing this because I need to access it in WidgetKit also)?
This is how I am accessing it in AppIntentTimelineProvider:
struct MediumShoeStatsTimelineProvider: AppIntentTimelineProvider {
let modelContext = ModelContext(ShoesStore.container)
// rest of the code, where I am using the modelContext to fetch models
}
Later edit:
I tried changing from custom MigrationState to lightweight and it's working. Except the fact the defaultRunTypes is empty for all shoes, as expected, since this is the default value.
So the way I am making the custom MigrationState is wrong I guess.
static let migrateV1toV2 = MigrationStage.lightweight(fromVersion: ShoesSchemaV1.self, toVersion: ShoesSchemaV2.self)
Later edit2: I have SwiftData iCloud sync enabled. I tried disabling it and the custom migration works without any issue.
Any idea how to make it work with iCloud sync enabled?
-com.apple.CoreData.SQLDebug 3