Post

Replies

Boosts

Views

Activity

SwiftData Migration Crashes on First Run
I'm having an issue with SwiftData and migrating schemas, whenever going from the V2 to V3 schema my app crashes with the following error on first launch: The managed object model version used to open the persistent store is incompatible with the one that was used to create the persistent store. After the first launch crash, then the app runs fine, and I confirmed that the schema was upgraded as expected. I'd like to get this resolved so that future migrations are seamless. Has anyone run into this or have any recommendations to resolve this? V2 Schema public enum DistanceTrackSchemaV2: VersionedSchema { public static var versionIdentifier = Schema.Version(2, 0, 0) public static var models: [any PersistentModel.Type] { [DistanceTrackSchemaV2.DistanceGoal.self] } @Model public final class DistanceGoal: Sendable { // swiftformat:disable all public let id: UUID = UUID() public var title: String = "" // swiftformat:disable all public var startingDate: Date = Date.distantPast // swiftformat:disable all public var endingDate: Date = Date.distantFuture public var distance: Double = -100 public var distanceSoFar: Double = 0 public var unit: String = DistanceMetric.miles.rawValue @Transient public var workouts: [DistanceWorkout] = [] @Transient public var shouldRefresh: Bool = false public init( title: String, startingDate: Date, endingDate: Date, unit: DistanceMetric, distance: Double ) { self.title = title self.startingDate = startingDate self.endingDate = endingDate self.unit = unit.rawValue self.distance = distance } } } V3 Schema public enum DistanceTrackSchemaV3: VersionedSchema { public static var versionIdentifier = Schema.Version(2, 1, 1) public static var models: [any PersistentModel.Type] { [DistanceTrackSchemaV3.DistanceGoal.self] } @Model public final class DistanceGoal: Sendable { // swiftformat:disable all public let id: UUID = UUID() public var title: String = "" // swiftformat:disable all public var startingDate: Date = Date.distantPast // swiftformat:disable all public var endingDate: Date = Date.distantFuture public var distance: Double = -100 public var distanceSoFar: Double = 0 public var unit: String = DistanceMetric.miles.rawValue public var rawWorkoutTypes: [String] = [ WorkoutType.walking.rawValue, WorkoutType.running.rawValue, WorkoutType.wheelchairRunPace.rawValue, WorkoutType.wheelchairWalkPace.rawValue, ] @Transient public var workouts: [DistanceWorkout] = [] public init( title: String, startingDate: Date, endingDate: Date, unit: DistanceMetric, distance: Double, workoutTypes: [WorkoutType] ) { self.title = title self.startingDate = startingDate self.endingDate = endingDate self.unit = unit.rawValue self.distance = distance self.rawWorkoutTypes = workoutTypes.map({$0.rawValue}) } } } Migration Plan public typealias DistanceSchema = DistanceTrackSchemaV3 public typealias DistanceGoal = DistanceSchema.DistanceGoal enum DistanceTrackMigrationPlan: SchemaMigrationPlan { static var schemas: [VersionedSchema.Type] { [ DistanceTrackSchemaV1.self, DistanceTrackSchemaV2.self, DistanceTrackSchemaV3.self, ] } static var stages: [MigrationStage] { [ migrateV1toV2, migrateV2toV210, ] } static let migrateV1toV2 = MigrationStage.lightweight( fromVersion: DistanceTrackSchemaV1.self, toVersion: DistanceTrackSchemaV2.self ) static let migrateV2toV210 = MigrationStage.custom( fromVersion: DistanceTrackSchemaV2.self, toVersion: DistanceTrackSchemaV3.self ) { _ in } didMigrate: { context in let goals = try? context.fetch( FetchDescriptor<DistanceTrackSchemaV3.DistanceGoal>() ) goals?.forEach { goal in goal.rawWorkoutTypes = [ WorkoutType.walking.rawValue, WorkoutType.running.rawValue, WorkoutType.wheelchairRunPace.rawValue, WorkoutType.wheelchairWalkPace.rawValue, ] } try? context.save() } } ModelContainer Creation public extension ModelContainer { private enum Constants { static var AppGroup = "group.XXXXXXXXXX" static var CloudKitContainerName = "XXXXXXXXXX" static var CloudContainer = "iCloud.XXXXXXXXXX" static var SQLFile = "XXXXXXXXXX-Shared.sqlite" } static var DistanceTrackContainer: ModelContainer = { do { let config: ModelConfiguration = .init( Constants.CloudKitContainerName, groupContainer: .identifier(Constants.AppGroup), cloudKitDatabase: .private(Constants.CloudContainer) ) let container = try ModelContainer( for: DistanceGoal.self, migrationPlan: DistanceTrackMigrationPlan.self, configurations: config ) return container } catch { fatalError("Failed to configure SwiftData container. Error: \(error)") } }() } Adding Model Container to View @main struct Distance_TrackApp: App { @Environment(\.scenePhase) var scenePhase var body: some Scene { WindowGroup { ContentView() .modelContainer(.DistanceTrackContainer) .onChange(of: scenePhase) { _, phase in switch phase { case .background: Task { await WidgetUpdateService.shared.refresh() } default: // Do Nothing break } } } } }
2
1
625
Feb ’24