iCloud & Data

RSS for tag

Learn how to integrate your app with iCloud and data frameworks for effective data storage

CloudKit Documentation

Post

Replies

Boosts

Views

Activity

Collaboration Preview Image and Title for CKShare When Collaborating With CloudKit
I recently updated our CloudKit collaboration invite codebase to use the new UIActivityController and NSItemProvider invitation as described in Apple's documentation. We previously used UICloudSharingController's init(preparationHandler:), which is since deprecated. We have all of the previous functionality in place: we successfully create a CKShare, send the invite out, engage the share, and collaborate. However, we cannot get the Messages CKShare preview to use our custom image and title (henceforth referred to as “collaboration metadata”). Previously, while using UICloudSharingController's init(preparationHandler:) to commence the share invite, the collaboration metadata successfully displayed in the Messages conversation. Now, we have a generic icon of our app and “Shared with App-Name" title, leading to a loss of contextual integrity for the invite flow. My question: How do we make the collaboration metadata appear in the Messages conversation? Here is our code for creating the UIActivityController, NSItemProvider, CKShare, and other related entities. It encapsulates the entire CloudKit CKShare invite setup. You will note that we do configure the CKShare with metadata, and we do set the LPLinkMetadata on the UIActivityItemsConfiguration. GitHub Gist. The metadata does successfully appear in the UIActivityController and the CKShare's image and title are available to the person receiving the share once they engage it and open it in our app – but the Messages preview item retains the generic message content. Also please note that this issue does occur in the production environment. As a final note, examining UICloudSharingController's definition leads me to believe that supplying a UIActivityItemSource is the key to getting correct Messages collaboration metadata in place. My efforts at using an item adhering to UIActivityItemSource in the UIActivityViewController used to send the share did not yield the rich previews and displayed metadata I am aiming for.
0
0
22
2h
iOS 18 SwiftData ModelContext reset
Since the iOS 18 and Xcode 16, I've been getting some really strange SwiftData errors when passing @Model classes around. The error I'm seeing is the following: SwiftData/BackingData.swift:409: Fatal error: This model instance was destroyed by calling ModelContext.reset and is no longer usable. PersistentIdentifier(id: SwiftData.PersistentIdentifier.ID(url: x-coredata://34EE9059-A7B5-4484-96A0-D10786AC9FB0/TestApp/p2), implementation: SwiftData.PersistentIdentifierImplementation) The same issue also happens when I try to retrieve a model from the ModelContext using its PersistentIdentifier and try to do anything with it. I have no idea what could be causing this. I'm guessing this is just a bug in the iOS 18 Beta, since I couldn't find a single discussion about this on Google, I figured I'd mention it. if someone has a workaround or something, that would be much appreciated.
12
8
541
3w
ContentsOfDirectory for FileProvider URLs
I'm developing an iOS app. Through a .fileImporter I get access to a directory URL which might be provided by an external service like Dropbox (using a FileProvider). As the user picked the URL I have full access to it. I want to get the contents of the directory: FileManager.default.contentsOfDirectory( at: url, includingPropertiesForKeys: keys, options: [.skipsHiddenFiles] ) Unfortunately I only get whatever files the system is currently aware of, which in case of external providers is often not much. If the user opens the Apple Files app and navigates to the folder then the system gets the content from the external provider and back in my app contentsOfDirectory would show everything. What can I do to get a list of the URLs? Can I somehow trigger the system to update from the external provider? Or is there another way of handling this situation?
0
0
35
9h
Importing Data into SwiftData in the Background Using ModelActor and @Query
I have an app with fairly typical requirements - I need to insert some data (in my case from the network but could be anything) and I want to do it in the background to keep the UI responsive. I'm using SwiftData. I've created a ModelActor that does the importing and using the debugger I can confirm that the data is indeed being inserted. On the UI side, I'm using @Query and a SwiftUI List to display the data but what I am seeing is that @Query is not updating as the data is being inserted. I have to quit and re-launch the app in order for the data to appear, almost like the context running the UI isn't communicating with the context in the ModelActor. I've included a barebones sample project. To reproduce the issue, tap the 'Background Insert' button. You'll see logs that show items being inserted but the UI is not showing any data. I've tested on the just released iOS 18b3 seed (22A5307f). The sample project is here: https://hanchor.s3.amazonaws.com/misc/SwiftDataBackgroundV2.zip
2
1
153
5d
Context.insert() but at index 0
Hello! I am an inexperienced programmer learning swiftUI and swiftData so apologies in advance if this answer can be found elsewhere. I want to display a list of recent transcriptions, stored in modelContainer, in reversed order. I attempted to do transcriptionsList.reversed() in a ForEach (which is in a swiftUI List), but .onDelete gets messed up as it is deleting the items as per the normal order. The question is, is there a way when using context.insert(), that I can specify to insert at index 0? Or is there a better way of resolving this? Thanks in advance! The code when pasted in here appears to not be formatted correctly so I didn't include code
1
0
33
20h
SwiftData does not work on a background Task even inside a custom ModelActor.
I have created an actor for the ModelContainer, in order to perform a data load when starting the app in the background. For this I have conformed to the ModelActor protocol and created the necessary elements, even preparing for test data. Then I create a function of type async throws to perform the database loading processes and everything works fine, in that the data is loaded and when loaded it is displayed reactively. actor Container: ModelActor { nonisolated let modelContainer: ModelContainer nonisolated let modelExecutor: ModelExecutor static let modelContainer: ModelContainer = { do { return try ModelContainer(for: Empleados.self) } catch { fatalError() } }() let context: ModelContext init(container: ModelContainer = Container.modelContainer) { self.modelContainer = container let context = ModelContext(modelContainer) self.modelExecutor = DefaultSerialModelExecutor(modelContext: context) self.context = context Task { do { try await loadData() } catch { print("Error en la carga \(error)") } } } } The problem is that, in spite of doing the load inside a Task and that there is no problem, when starting the app it stops responding the UI while loading to the user interactions. Which gives me to understand that actually the task that should be in a background thread is running somehow over the MainActor. As I have my own API that will provide the information to my app and refresh it at each startup or even send them in Batch when the internet connection is lost and comes back, I don't want the user to be continuously noticing that the app stops because it is performing a heavy process that is not really running in the background. Tested and compiled on Xcode 15 beta 7. I made a Feedback for this: FB13038621. Thanks Julio César
7
1
3.9k
Aug ’23
Non-Optional One-To-Many Relationship Missing Object In Array
I'm encountering an issue with a one-to-many relationship between two models using SwiftData. Here's a simple example of my models: @Model final class TVSeries { var title: String @Relationship(deleteRule: .cascade, inverse: \Episode.tvSeries) var episodes: [Episode] init(title: String, episodes: [Episode] = []) { self.title = title self.episodes = episodes } } @Model final class Episode { var title: String var tvSeries: TVSeries // <- Works fine when optional init(title: String, tvSeries: TVSeries) { self.title = title self.tvSeries = tvSeries } } After creating and saving a TVSeries instance with an associated Episode, fetching the TVSeries instance shows that the episodes array remains empty whereas the back-link to the TVSeries works as expected. Here's the relevant test case: struct SwiftDataTestingTests { @Test func testFullInit() async throws { // Configure SwiftData context let config = ModelConfiguration(isStoredInMemoryOnly: true) let container = try ModelContainer(for: TVSeries.self, Episode.self, configurations: config) let context = ModelContext(container) context.autosaveEnabled = false // Create entries let tvSeries = TVSeries(title: "New Series") let episode = Episode(title: "Episode 1", tvSeries: tvSeries) context.insert(episode) try context.save() // Fetch tv series let tvSeriesDescriptor = FetchDescriptor<TVSeries>() let tvSeriesResult = try context.fetch(tvSeriesDescriptor) #expect(tvSeriesResult.count == 1) let fetchedTVSeries = try #require(tvSeriesResult.first) #expect(fetchedTVSeries.episodes.count == 1) // <- Expectation failed: (fetchedTVSeries.episodes.count → 0) == 1 // Fetch episodes let episodeDescriptor = FetchDescriptor<Episode>() let episodeResult = try context.fetch(episodeDescriptor) #expect(episodeResult.count == 1) let fetchedEpisode = try #require(episodeResult.first) #expect(fetchedEpisode.tvSeries.title == "New Series") } } Everything seems fine when I make the tvSeries attribute in the Episode model optional, but I would prefer to leave it explicit. I tested this on the latest XCode and XCode Beta versions running macOS Sonoma XCode Version: 15.4 (15F31d) XCode Beta Version: 16.0 beta 3 (16A5202i) MacOS Version: 14.5 (23F79) Any insights or suggestions on what might be causing this issue would be greatly appreciated. Thank you!
0
0
58
1d
SwiftData - error: Error: Persistent History (2) has to be truncated due to the following entities being removed: ( A )
I'm having trouble resolving the error. If I save data in view A and open view B, I get an error. If I open only view A, or only view B, there is no problem, but when I open a second view (A to B, B to A, etc.) I get an error. Attached is the code that reproduces the problem. We would appreciate your cooperation. No simulator is used. Xcode: Version 15.4 (15F31d) iPone11: 17.5.1 Thank you. Error error: Error: Persistent History (2) has to be truncated due to the following entities being removed: ( A ) warning: Warning: Dropping Indexes for Persistent History warning: Warning: Dropping Transactions prior to 2 for Persistent History warning: Warning: Dropping Changes prior to TransactionID 2 for Persistent History Code import SwiftUI import SwiftData @main struct issueApp: App { var body: some Scene { WindowGroup { ContentView() .modelContainer(for: [A.self, B.self]) } } } import SwiftUI import SwiftData struct ContentView: View { @State var showAView: Bool = false @State var showBView: Bool = false var body: some View { VStack { Button("show A"){ showAView.toggle() } .sheet(isPresented: $showAView){ AView() } Button("show B"){ showBView.toggle() } .sheet(isPresented: $showBView){ BView() } } } } struct AView: View { @Environment(\.modelContext) var context @Query private var list: [A] let query = QueryData<A>() var body: some View { VStack { if list.count > 0 { VStack { List { ForEach(list, id: \.number){ item in VStack { Text(item.number) } } } Button("clear"){ try? context.delete(model: A.self) } } } else { Button("set"){ query.set(data: A(number: "1")) } } } } } struct BView: View { @Environment(\.modelContext) var context @Query private var list: [B] let query = QueryData<B>() var body: some View { VStack { if list.count > 0 { VStack { List { ForEach(list, id: \.number){ item in VStack { Text(item.number) } } } Button("clear"){ try? context.delete(model: B.self) } } } else { Button("set"){ query.set(data: B(number: "1")) } } } } } class QueryData<T: PersistentModel> { private var container: ModelContainer? init() { self.container = try? ModelContainer(for: T.self) } func set(data: T) { guard let container = self.container else { return } let context = ModelContext(container) do { try context.delete(model: T.self) context.insert(data) try context.save() } catch { print("error: \(error)") } } } @Model final class A { var number: String init(number: String) { self.number = number } } @Model final class B { var number: String init(number: String) { self.number = number } }
0
0
64
1d
SwiftData update value
Hi I want to update item value after insert. Solution(False): I want to add "isAutosaveEnabled" prop to modelContainer, but modelContainer just have one parm. Solution2(False): There doesn't have API to update SwiftData, I just find insert, delete and save. I change the city_name and run "try? modelContext.save()". But it replace after I reopen the app and maybe it doesn't work even before I close app. How can I update the city_name? /// --------- App --------- import SwiftUI import SwiftData @main struct MyApp: App { var container: ModelContainer init(){ let schema = Schema([ SD_City.self, ]) let modelConfiguration = ModelConfiguration(schema: schema) do { container = try ModelContainer(for: schema, configurations: [modelConfiguration]) let context = ModelContext(container) var city : SD_City city = SD_City(city_id: 1, city_name: "city1") context.insert(city) city = SD_City(city_id: 2, city_name: "city2") context.insert(city) } catch { fatalError("Could not create ModelContainer: ") } } var body: some Scene { WindowGroup { ContentView() } .modelContainer(container) } } /// --------- ContentView --------- import SwiftUI import SwiftData struct ContentView: View { @Environment(\.modelContext) private var modelContext @State var sortOrder_city = SortDescriptor(\SD_City.city_id) @Query(sort: [SortDescriptor(\SD_City.city_id)]) var citylist: [SD_City] @State var city_id = 0 @State var city_name = "city_name" var body: some View { HStack { Button("Change \ncity name", action: { // Update the city_name of city1 to city3 // SD_City(city_id: 1, city_name: "city1") --> SD_City(city_id: 1, city_name: "city3") }) Text(city_name) } } } /// ---------swiftdata--------- import SwiftUI import SwiftData @Model final class SD_City{ @Attribute(.unique) let city_id: Int = 1 var city_name: String = "" init(city_id: Int , city_name: String) { self.city_id = city_id self.city_name = city_name } } Thanks~
0
0
61
1d
Public and Private CK sync failure, macOS mistake = add data before indexing
I have a Multiplatform app for iOS and macOS targets. I am using CloudKit with CoreData and have successfully established a private and public database. The app has successfully synced private and public data for months between macOS (dev machine), an iPhone 13 Pro and an iPad Pro 12.9inch 2nd gen. The public data also syncs perfectly to simulator instances running under other iCloud accounts. Recently I added a new entity in the public DB and here is where I seemed to have made a mistake. I entered data into the new public database via my developer UI built into the macOS app running on my MBP before I indexed the necessary fields. Side note - I find it necessary to index the following for each Entity to ensure iCloud sync works as expected on all devices... modifiedTimestamp - Queryable modifiedTimestamp - Sortable recordName - Queryable Realising my mistake, I indexed the above CKRecord fields for the new Entity. Since then, the macOS target has remained in some way "frozen" (for want of a better term). I can add new public or private records in the macOS app but they do not propagate to the public or private stores in iCloud. I have attempted many fixed, some summarised below: clean build folder from Xcode; remove all files from the folder /Users//Library/Containers/, place in recycle bin, empty recycle bin, then build and run; build and run on iPhone and iPad targets to ensure all apps are current dev version, then repeat above processes. I've read through the console logging when I build and run the macOS app many many times to see whether I can find any hint. The closest thing I can find is... BOOL _NSPersistentUIDeleteItemAtFileURL(NSURL *const __strong) Failed to stat item: file:///Users/<me>/Library/Containers/com.me.AppName/Data/Library/Saved%20Application%20State/com.me.AppName.savedState/restorecount.plist but my research on this and other forums suggests this is not relevant. Through this, the app still functions as expected on iOS devices and both private and public database additions and modifications propagate to iCloud stores and to other devices. I expect that removing the macOS app entirely from my dev machine would trigger a complete sync with all existing data. Imagine I bought a new macOS device and chose to install my app where before I had run this only on my iOS devices. My current problem suggests that I could not do this, but I know that this is not the intended behaviour. This scenario makes me think there is a setting file for my macOS app that I'm not aware of and that this impeding the sync of all existing app data back to the fresh install of the macOS app? But that is a wild guess. Running public releases (no betas) Xcode 15.4 (15F31d) macOS Sonoma 14.5 physical iOS devices running iOS 17.5.1 Any words of wisdom on how I might go about trying to solve this problem please?
0
0
65
1d
Crash by SwiftData MigarionPlan
I am a develop beginner. Recently, my App used SwiftData's MigraitonPlan, which caused it to crash when I opened it for the first time after updating in TestFlight or the store. After clicking it a second time, I could enter the App normally, and I could confirm that all the models were the latest versions.However, when I tested it in Xcode, everything was normal without any errors. Here is my MigrationPlan code: import Foundation import SwiftData enum MeMigrationPlan: SchemaMigrationPlan { static var schemas: [any VersionedSchema.Type] { [MeSchemaV1.self, MeSchemaV2.self, MeSchemaV3.self, MeSchemaV4.self] } static var stages: [MigrationStage] { [migrateV1toV2, migrateV2toV3, migrateV3toV4] } //migrateV1toV2, because the type of a data field in MeSchemaV1.TodayRingData.self was modified, the historical data was deleted during the migration, and the migration work was successfully completed. static let migrateV1toV2 = MigrationStage.custom( fromVersion: MeSchemaV1.self, toVersion: MeSchemaV2.self, willMigrate: { context in try context.delete(model: MeSchemaV1.TodayRingData.self) }, didMigrate: nil ) //migrateV2toV3, because a new Model was added, it would crash at startup when TF and the official version were updated, so I tried to delete the historical data during migration, but the problem still exists. static let migrateV2toV3 = MigrationStage.custom( fromVersion: MeSchemaV2.self, toVersion: MeSchemaV3.self, willMigrate: { context in try context.delete(model: MeSchemaV2.TodayRingData.self) try context.delete(model: MeSchemaV2.HealthDataStatistics.self) try context.delete(model: MeSchemaV2.SportsDataStatistics.self) try context.delete(model: MeSchemaV2.UserSettingTypeFor.self) try context.delete(model: MeSchemaV2.TodayRingData.self) try context.delete(model: MeSchemaV2.TodayHealthData.self) try context.delete(model: MeSchemaV2.SleepDataSource.self) try context.delete(model: MeSchemaV2.WorkoutTargetData.self) try context.delete(model: MeSchemaV2.WorkoutStatisticsForTarget.self) try context.delete(model: MeSchemaV2.HealthDataList.self) }, didMigrate: nil ) //migrateV3toV4, adds some fields in MeSchemaV3.WorkoutList.self, and adds several new Models. When TF and the official version are updated, it will crash at startup. Continue to try to delete historical data during migration, but the problem still exists. static let migrateV3toV4 = MigrationStage.custom( fromVersion: MeSchemaV3.self, toVersion: MeSchemaV4.self, willMigrate: { context in do { try context.delete(model: MeSchemaV3.WorkoutList.self) try context.delete(model: MeSchemaV3.HealthDataStatistics.self) try context.delete(model: MeSchemaV3.SportsDataStatistics.self) try context.delete(model: MeSchemaV3.UserSettingTypeFor.self) try context.delete(model: MeSchemaV3.TodayRingData.self) try context.delete(model: MeSchemaV3.TodayHealthData.self) try context.delete(model: MeSchemaV3.SleepDataSource.self) try context.delete(model: MeSchemaV3.WorkoutTargetData.self) try context.delete(model: MeSchemaV3.WorkoutStatisticsForTarget.self) try context.delete(model: MeSchemaV3.HealthDataList.self) try context.delete(model: MeSchemaV3.SleepStagesData.self) try context.save() } catch { print("Migration from V3 to V4 failed with error: \(error)") throw error } }, didMigrate: nil ) }
18
0
340
2w
SwiftData: externalStorage attribute on ios18
Hello, I'm experiencing an issue with iOS 18 Beta 3 and SwiftData. I have a model with some attributes marked as externalStorage because they are pretty large. In iOS 17 I could display a list of all my Models and it would not prefetch the externalStorage attributes. Now in iOS 18 it seems the attributes are fetched even though I don't use them in my list. It's an issue because the memory use of my app goes from 100mo on iOS 17 to more than 1gb on iOS 18 because of this. My app is configured to sync with iCloud. Anyone else experiencing the issue? Thanks Gil
1
1
101
5d
Tabs with SwiftData
Hello folks, absolute newbie here. I'm following a tutorial that I'm trying to adjust to my own needs. In the tutorial the app is sat up in a way that you can add a new entry to a note taking app by tapping on a button which brings in an edit view. What I'm trying to do is to bring in the edit view by adding it as a tab on the bottom instead. Here's my ContentView: import SwiftData import SwiftUI struct ContentView: View { let example = Entry(content: "Example Entry") var body: some View { TabView { LifeEventsView() .tabItem { Label("Life events", systemImage: "house") } EditEntryView(entry: example) .tabItem { Label("Add new event", systemImage: "plus") } MattersView() .tabItem { Label("What matters", systemImage: "house") } } } } #Preview { ContentView() } And here's the EditEntryView: import SwiftData struct EditEntryView: View { @Bindable var entry: Entry var body: some View { Form { TextField("Entry", text: $entry.content, axis: .vertical) } .navigationTitle("Edit entry") .navigationBarTitleDisplayMode(.inline) } } #Preview { do { let config = ModelConfiguration(isStoredInMemoryOnly: true) let container = try ModelContainer(for: Entry.self, configurations: config) let example = Entry(content: "Example Entry") return EditEntryView(entry: example) .modelContainer(container) } catch { fatalError("Failed to create model container.") } } The build succeeds but I get an error while running saying: Thread 1: Fatal error: failed to find a currently active container for Entry I'm sure this is an easy fix but I've been thinking with it for a while with no luck.
0
0
95
2d
SwiftData ModelConfiguration with GroupContainer shows Data Access alert on every launch
I'm building an app which has both iOS and macOS versions along with extensions on both platforms. I want the main app to be able to share data the extension using the group container and I want the app on both platforms sync data over CloudKit. CloudKit synchronization works like a dream. The data sharing between the app and extension on iOS works also exactly as intended. However on macOS every time the app is launched I get “MyApp” would like to access data from other apps. alert dialog. I tried initializing the ModelConfiguration both with an explicit and automatic app group container identifiers. Same results.
1
0
106
5d
Nested Composite Attribute values are not saved in the database
According the WWDC23 presentation “What’s new in Core Data”, “Composite attributes may be nested within each other”. However, in the current macOS and iOS beta builds, only single level composite attributes (without nested composite attributes) are persisted in the SQLite database. Is this still work in progress? See example project in FB12552092.
1
0
514
Jul ’23
SwiftData Migration Plan does not run custom migration stage
Hi, I'm trying to make some changes to my SwiftData model and I want to add a new non-optional property to one of my model classes. My current model was not part of a VersionedSchema so I first encapsulated it into one public enum FeynnDataModelsSchemaV1: VersionedSchema { public static var versionIdentifier: Schema.Version = .init(1, 0, 0) public static var models: [any PersistentModel.Type] { [FeynnDataModelsSchemaV1.Workout.self, FeynnDataModelsSchemaV1.Activity.self, FeynnDataModelsSchemaV1.ActivityRecord.self, FeynnDataModelsSchemaV1.WorkoutSession.self] } } Then I run the app and everything works as expected. Secondly, I create the V2 and add the new property: public enum FeynnDataModelsSchemaV2: VersionedSchema { public static var versionIdentifier: Schema.Version = Schema.Version(2, 0, 0) public static var models: [any PersistentModel.Type] { [FeynnDataModelsSchemaV2.Workout.self, FeynnDataModelsSchemaV2.Activity.self, FeynnDataModelsSchemaV2.ActivityRecord.self, FeynnDataModelsSchemaV2.WorkoutSession.self] } } extension FeynnDataModelsSchemaV2 { @Model final public class Activity: Hashable { ... public var activityType: ActivityType = ActivityType.traditionalStrengthTraining ... } } Lastly, I create the schema migration plan and add it to my modelContainer: public enum FeynnDataModelsMigrationPlan: SchemaMigrationPlan { public static var schemas: [VersionedSchema.Type] = [ FeynnDataModelsSchemaV1.self, FeynnDataModelsSchemaV2.self ] public static var stages: [MigrationStage] = [migrateV1toV2] public static var migrateV1toV2 = MigrationStage.custom(fromVersion: FeynnDataModelsSchemaV1.self, toVersion: FeynnDataModelsSchemaV2.self, willMigrate: {moc in let activities = try? moc.fetch(FetchDescriptor<Activity>()) print("\(activities?.count ?? 919191991)") }, didMigrate: {moc in let activities = try? moc.fetch(FetchDescriptor<Activity>()) print("\(activities?.count ?? 88888888)") activities?.forEach { activity in activity.activityType = .traditionalStrengthTraining } try? moc.save() }) } if let container = try? ModelContainer( for: Workout.self, Activity.self, ActivityRecord.self, WorkoutSession.self, migrationPlan: FeynnDataModelsMigrationPlan.self, configurations: ModelConfiguration(cloudKitDatabase: .automatic)) { self.container = container } else { self.container = try ModelContainer( for: Workout.self, Activity.self, ActivityRecord.self, WorkoutSession.self, migrationPlan: FeynnDataModelsMigrationPlan.self, configurations: ModelConfiguration(cloudKitDatabase: .none)) } After running this, the application runs as expected, but as soon as I render a view that references Activity.activityType the app crashes trying to get the value for my existing activities given that it is nil, pointing out that the migration stage was not ran? None of the print statements in the didMigrate or willMigrate can be found within the logs either. I have tried several approaches creating more VersionedSchemas and I run into more issues such as Cannot use stuck migration with an unknown model version. I feel like the current way of handling schema versions of SwiftData is very confusing and opaque for the developer to know what is going on. I don't know if the underlying database is picking the un-versioned schema, the V1 or the V2. Is there anything I'm doing wrong? What can I do apart from making the new property optional and having a computed property that unwraps it giving it a default value when nil? Thank you!
3
0
420
Mar ’24
I'm having trouble applying the SwiftData MigrationPlan.
I'm currently using Swiftdata to store data for an app I've deployed to the app store. The problem is that the app does not build when I add a case of the Enum type to the model, so I decided to apply a MigrationPlan. I also decided that it is not a good idea to store the Enum type itself because of future side effects. The app is deployed in the app store with a model without a VersionedSchema, so the implementation is complete until we modify it to a model with a VersionedSchema. This is Version1. public enum TeamSchemaV1: VersionedSchema { public static var versionIdentifier: Schema.Version = .init(1, 0, 0) public static var models: [any PersistentModel.Type] { [TeamSchemaV1.Team.self, TeamSchemaV1.Lineup.self, TeamSchemaV1.Player.self, TeamSchemaV1.Human.self] } @Model public final class Lineup { ... public var uniform: Uniform ... } And you're having trouble migrating to Version2. The change is to rename the Uniform to DeprecatedUniform (the reason for the following change is to migrate even if it's just a property name) public enum TeamSchemaV2: VersionedSchema { public static var versionIdentifier: Schema.Version = .init(1, 0, 1) public static var models: [any PersistentModel.Type] { [TeamSchemaV2.Team.self, TeamSchemaV2.Lineup.self, TeamSchemaV2.Player.self, TeamSchemaV2.Human.self] } @Model public final class Lineup { ... @Attribute(originalName: "uniform") public var deprecatedUniform: Uniform ... When you apply this plan and build the app, EXC_BAD_ACCESS occurs. public enum TeamMigrationPlan: SchemaMigrationPlan { public static var schemas: [VersionedSchema.Type] { [TeamSchemaV1.self, TeamSchemaV2.self] } public static var stages: [MigrationStage] { [migrateV1toV2] } public static let migrateV1toV2 = MigrationStage.lightweight(fromVersion: TeamSchemaV1.self, toVersion: TeamSchemaV2.self) } I'm currently unable to migrate the data, which is preventing me from updating and promoting the app. If anyone knows of this issue, I would really appreciate your help.
1
0
445
Dec ’23
SwiftData custom migration crash
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?
5
0
245
1w
Multiple relationship variables of the the same type
Want to know if it's possible to have multiple variables of the same type? This code works but when the app loads the data again on relaunch, both variables have both the new cards and discarded cards. @Model class Player { let id = UUID() let name: String @Relationship(deleteRule: .cascade, inverse: \Card.player) var cards: [Card] = [] @Relationship(deleteRule: .cascade, inverse: \Card.player) var discardedCards: [Card] = [] init(name: String, cards: [Card]) { self.name = name self.cards = cards } } @Model class Card { let id = UUID() let name: String var player: Player? init(name: String) { self.name = name } }
0
0
66
5d