Post

Replies

Boosts

Views

Activity

Reply to Lightweight Migration Issues
One workaround (from a forum post I'll try to find!) is to create an intermediate model and copy over the data in a complex migration: enum TripsSchemaV1_0_0: VersionedSchema { static var versionIdentifier = Schema.Version(1, 0, 0) static var models: [any PersistentModel.Type] { [Trip.self] } @Model class Trip { var name: String init(name: String) { self.name = name } } } enum TripsSchemaV1_1_0: VersionedSchema { static var versionIdentifier = Schema.Version(1, 1, 0) static var models: [any PersistentModel.Type] { [Trip.self] } @Model class Trip { var name: String var title: String? // Migrated in schema v2.0 init(name: String, title: String? = nil) { self.name = name self.title = title } } } enum TripsSchemaV2_0_0: VersionedSchema { static var versionIdentifier = Schema.Version(2, 0, 0) static var models: [any PersistentModel.Type] { [Trip.self] } @Model class Trip { var title: String init(title: String) { self.title = title } } } Then in your MigrationPlan, move the data: enum TripsSchemaMigrationPlan: SchemaMigrationPlan { static var schemas: [any VersionedSchema.Type] { [ TripsSchemaV1_0_0.self, TripsSchemaV1_1_0.self, TripsSchemaV2_0_0.self, ] } static var stages: [MigrationStage] { [ v1_0_0_to_v0_1_1_lightweight, v1_1_0_to_v0_2_0_custom, ] } /// Filling in with `nil`, which SwiftData will happily do :) private static let v1_0_0_to_v0_1_1_lightweight = MigrationStage.lightweight( fromVersion: TripsSchemaV1_0_0.self, toVersion: TripsSchemaV1_1_0.self ) private static let v1_1_0_to_v0_2_0_custom = MigrationStage.custom( fromVersion: TripsSchemaV1_1_0.self, toVersion: TripsSchemaV2_0_0.self, willMigrate: { context in let trips = try context.fetch(FetchDescriptor<TripsSchemaV1_1_0.Trip>()) // Goal: fill-in the new name. for trip in trips { trip.title = trip.name } // Assuming autosave is on. }, didMigrate: nil ) }
Topic: App & System Services SubTopic: iCloud Tags:
Apr ’24
Reply to SwiftData errors when trying to insert multiple objects
Some ideas: Verify there isn't already an item with id: 1 when re-running mock code Verify anything with @Attribute(.unique) is ACTUALLY unique in mock code Make sure that any models that have a @Relationship have a corresponding deleteRule, if you're running a series of delete(...) elsewhere. The first one caught me off guard -- if running a series of delete calls on PersistentModel types, make sure that any models that have a @Relationship also have a corresponding deleteRule. When deleting from either end, SwiftData can null out the child in its parent (.nullify), or delete its parent altogether (.cascade). Without this, a hanging reference will be left, causing null value errors. For example: @Model struct Aisle { @Relationship(deleteRule: .nullify, inverse: \Item.aisle) var items: [Item] } @Model struct Item { @Relationship(deleteRule: .nullify) // works! var aisle: Aisle } Also, save the model context before running deletes. Otherwise, changes will be flushed from the cache after the delete is ran, causing persistence issues. The joy of caching!
Topic: App & System Services SubTopic: iCloud Tags:
Feb ’24
Reply to Sections in SwiftUI Tables?
I was able to do this by adding Section for the @TableColumnBuilder in this Table initializer. A (reduced) code sample: struct ImportTrackTable: View { @EnvironmentObject var store: Store @State var selectedTracks: Set<Track.ID> = .init() // Sorting @State private var sortOrder: [KeyPathComparator<Binding<Track>>] = [ .init(\.title.wrappedValue, order: .forward), ] private let titleComparator = KeyPathComparator<Track>(\.title, order: .forward) var body: some View { Table( of: Binding<Track>.self, selection: $selectedTracks, sortOrder: $sortOrder ) { TableColumn( "Title", value: \Binding<Track>.wrappedValue, comparator: titleComparator ) { trackBinding in TrackTextField(text: trackBinding.title) } TableColumn( "Genres", value: \Binding<Track>.wrappedValue.genreList ) } rows: { Section("Unassigned") { ForEach( $store.tracks .sorted(using: sortOrder) .filter({ track in // Cool filter logic }) ) { $track in TableRow($track) .draggable($track.wrappedValue) } } Section("Assigned") { ForEach( $store.tracks .sorted(using: sortOrder) ) { $track in TableRow($track) .draggable($track.wrappedValue) } } } } }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jan ’24
Reply to Lightweight Migration Issues
One workaround (from a forum post I'll try to find!) is to create an intermediate model and copy over the data in a complex migration: enum TripsSchemaV1_0_0: VersionedSchema { static var versionIdentifier = Schema.Version(1, 0, 0) static var models: [any PersistentModel.Type] { [Trip.self] } @Model class Trip { var name: String init(name: String) { self.name = name } } } enum TripsSchemaV1_1_0: VersionedSchema { static var versionIdentifier = Schema.Version(1, 1, 0) static var models: [any PersistentModel.Type] { [Trip.self] } @Model class Trip { var name: String var title: String? // Migrated in schema v2.0 init(name: String, title: String? = nil) { self.name = name self.title = title } } } enum TripsSchemaV2_0_0: VersionedSchema { static var versionIdentifier = Schema.Version(2, 0, 0) static var models: [any PersistentModel.Type] { [Trip.self] } @Model class Trip { var title: String init(title: String) { self.title = title } } } Then in your MigrationPlan, move the data: enum TripsSchemaMigrationPlan: SchemaMigrationPlan { static var schemas: [any VersionedSchema.Type] { [ TripsSchemaV1_0_0.self, TripsSchemaV1_1_0.self, TripsSchemaV2_0_0.self, ] } static var stages: [MigrationStage] { [ v1_0_0_to_v0_1_1_lightweight, v1_1_0_to_v0_2_0_custom, ] } /// Filling in with `nil`, which SwiftData will happily do :) private static let v1_0_0_to_v0_1_1_lightweight = MigrationStage.lightweight( fromVersion: TripsSchemaV1_0_0.self, toVersion: TripsSchemaV1_1_0.self ) private static let v1_1_0_to_v0_2_0_custom = MigrationStage.custom( fromVersion: TripsSchemaV1_1_0.self, toVersion: TripsSchemaV2_0_0.self, willMigrate: { context in let trips = try context.fetch(FetchDescriptor<TripsSchemaV1_1_0.Trip>()) // Goal: fill-in the new name. for trip in trips { trip.title = trip.name } // Assuming autosave is on. }, didMigrate: nil ) }
Topic: App & System Services SubTopic: iCloud Tags:
Replies
Boosts
Views
Activity
Apr ’24
Reply to SwiftData errors when trying to insert multiple objects
Some ideas: Verify there isn't already an item with id: 1 when re-running mock code Verify anything with @Attribute(.unique) is ACTUALLY unique in mock code Make sure that any models that have a @Relationship have a corresponding deleteRule, if you're running a series of delete(...) elsewhere. The first one caught me off guard -- if running a series of delete calls on PersistentModel types, make sure that any models that have a @Relationship also have a corresponding deleteRule. When deleting from either end, SwiftData can null out the child in its parent (.nullify), or delete its parent altogether (.cascade). Without this, a hanging reference will be left, causing null value errors. For example: @Model struct Aisle { @Relationship(deleteRule: .nullify, inverse: \Item.aisle) var items: [Item] } @Model struct Item { @Relationship(deleteRule: .nullify) // works! var aisle: Aisle } Also, save the model context before running deletes. Otherwise, changes will be flushed from the cache after the delete is ran, causing persistence issues. The joy of caching!
Topic: App & System Services SubTopic: iCloud Tags:
Replies
Boosts
Views
Activity
Feb ’24
Reply to Sections in SwiftUI Tables?
I was able to do this by adding Section for the @TableColumnBuilder in this Table initializer. A (reduced) code sample: struct ImportTrackTable: View { @EnvironmentObject var store: Store @State var selectedTracks: Set<Track.ID> = .init() // Sorting @State private var sortOrder: [KeyPathComparator<Binding<Track>>] = [ .init(\.title.wrappedValue, order: .forward), ] private let titleComparator = KeyPathComparator<Track>(\.title, order: .forward) var body: some View { Table( of: Binding<Track>.self, selection: $selectedTracks, sortOrder: $sortOrder ) { TableColumn( "Title", value: \Binding<Track>.wrappedValue, comparator: titleComparator ) { trackBinding in TrackTextField(text: trackBinding.title) } TableColumn( "Genres", value: \Binding<Track>.wrappedValue.genreList ) } rows: { Section("Unassigned") { ForEach( $store.tracks .sorted(using: sortOrder) .filter({ track in // Cool filter logic }) ) { $track in TableRow($track) .draggable($track.wrappedValue) } } Section("Assigned") { ForEach( $store.tracks .sorted(using: sortOrder) ) { $track in TableRow($track) .draggable($track.wrappedValue) } } } } }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Replies
Boosts
Views
Activity
Jan ’24