Running the app on a physical device didn't solve it for me. Looks like there is a workaround here: https://github.com/JuniperPhoton/Widget-Intermediate-Animation/issues/1. I'm unable to get the separate model context to work with the @Query though, so in my case I'd have to abandon @Query which adds a lot of code to make sure the view responds to updates from different screens in the app.
Post
Replies
Boosts
Views
Activity
I just ran into a similar problem in my app. After hours of looking around, I couldn't find what I was looking for, but I found a solution that works... Hopefully there is a better solution though.
Problem
Schema v1
public enum SchemaV1: VersionedSchema {
public static var versionIdentifier: Schema.Version = .init(1, 0, 0)
public static var models: [any PersistentModel.Type] {
[Reminder.self]
}
@Model
public final class Reminder: Hashable, Identifiable {
public var id: UUID
public var title: String
public var icon: ReminderIconStyle
// This is an enum, that I want to convert to a struct, so I need to migrate to the new model.
public var style: ReminderTimeStyle
...
}
}
Schema v2
public enum SchemaV2: VersionedSchema {
public static var versionIdentifier: Schema.Version = .init(2, 0, 0)
public static var models: [any PersistentModel.Type] {
[Reminder.self]
}
@Model
public final class Reminder: Hashable, Identifiable {
public var id: UUID
public var title: String
public var icon: ReminderIconStyle
// New to V2
public var recurrenceRule: RecurrenceRule?
// New to V2
@Attribute(.transformable(by: DateComponentsTransformer.self))
public var startDate: DateComponents
// New to V2
@Attribute(.transformable(by: DateComponentsTransformer.self))
public var dueDate: DateComponents
...
}
}
I haven't had to do any migrations in the past, since I have only added additional properties. Now I need to remove a property, and convert it to 3 new properties.
Remove
var style: ReminderTimeStyle
Add
var recurrenceRule: RecurrenceRule?
var startDate: DateComponents
var dueDate: DateComponents
In order for me to add the new properties, I need access to style: ReminderTimeStyle so that I can get the relevant values to construct the new properties. The lightweight migration obviously failed, since there are major changes. So I started adding the logic for the custom migration.
First custom migration attempt
enum MigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[SchemaV1.self, SchemaV2.self]
}
static var stages: [MigrationStage] {
[.migrateV1toV2]
}
}
extension MigrationStage {
static let migrateV1toV2 = MigrationStage.custom(
fromVersion: SchemaV1.self,
toVersion: SchemaV2.self,
willMigrate: { context in
let calendar = Calendar.current
let oldReminders = try? context.fetch(FetchDescriptor<SchemaV1.Reminder>())
for old in oldReminders ?? [] {
var startDate: DateComponents!
var dueDate: DateComponents!
var recurrenceRule: RecurrenceRule?
switch old.style {
case let .manual(start, duration):
startDate = calendar.dateComponents(in: .current, from: start)
dueDate = calendar.dateComponents(in: .current, from: start.advanced(by: duration))
case let .repeat(r):
startDate = calendar.dateComponents(in: .current, from: r.startDate)
dueDate = calendar.dateComponents(in: .current, from: r.endDate)
recurrenceRule = .init(frequency: r.frequency, interval: Int(r.every))
}
let new = SchemaV2.Reminder(
id: old.id,
title: old.title,
icon: old.icon,
startDate: startDate,
dueDate: dueDate,
recurrenceRule: recurrenceRule,
...
)
context.insert(new)
context.delete(old)
}
try? context.save()
}, didMigrate: { context in
print("Migrated!", context.container)
}
)
}
Every time I would run the migration using that code, it would always fail when creating SchemaV2.Reminder with some weird error messages like:
Fatal error: Expected only Arrays for Relationships - RecurrenceRule
Fatal error: Unexpected type for CompositeAttribute: Optional
So I thought the problem was with the new property. It turns out that it was failing on all of the new properties, and if I changed the order it would crash on whichever one was first.
Solution
I found that you only have access to the "old" models in willMigrate, and you only have access to the "new" models in didMigrate. And you can't just move the migration code into the didMigrate block, because the breaking schema changes have to be fixed before didMigrate will run. So there is no way to do the type of migration that I was attempting. That means I have to leave the old database table around for this migration, so I have access to both models. I assume I can remove the table in the next schema version.
Move the willMigrate code into the didMigrate block so that we have access to the new Reminder model.
Rename the SchemaV2.Reminder model to something else i.e. SchemaV2.Reminder2
Make sure both SchemaV1.Reminder and SchemaV2.Reminder2 are included in the SchemaV2.models array.
public typealias CurrentSchema = SchemaV2
public typealias Reminder = CurrentSchema.Reminder2
public enum SchemaV2: VersionedSchema {
public static var versionIdentifier: Schema.Version = .init(2, 0, 0)
public static var models: [any PersistentModel.Type] {
[SchemaV1.Reminder.self, Reminder2.self]
}
@Model
public final class Reminder2: Hashable, Identifiable {
...
}
}
extension MigrationStage {
static let migrateV1toV2 = MigrationStage.custom(
fromVersion: SchemaV1.self,
toVersion: SchemaV2.self,
willMigrate: nil,
didMigrate: { context in
...
}
)
}
Drawbacks
Reminder can't be used as a model name, since that refers to the original table in the database. The typealias helps make that not as bad.
The original Reminder table still exists in the database, it just won't be used. You can remove it, but it will take an additional migration stage.
The migration code is not as straightforward as it should be.
Highlights
willMigrate is where logic goes if you can migrate your data without creating new models and the migration is pretty simple. Breaking model changes have to be fixed here.
didMigrate is where logic goes if you need access to the new model, but you can't access old models from here.
Since I needed access to the old model and the new one in order to do the migration I had to keep the old model around.
I hope this helps someone!
I am seeing the same problem. I have an M1 MacBook Air, iPad Pro, and an iMac. The MacBook Air has 2 accounts with the same iCloud account, it seems to associate a device to each account. The iMac only has 1 account.
Account 1 connects to the iMac, but not the iPad.
Account 2 connects to the iPad, but not the iMac.
After many reboots and log-outs of each device eventually it did work. Now all 3 devices are connected and functioning properly. It took about 30 minutes of trial and error before I could get them all to connect.
I'm having the same problem. But I do want my application available on Apple silicon Macs. I thought that after macOS 12 was released the errors would go away. But that hasn't been the case.