iCloud & Data

RSS for tag

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

CloudKit Documentation

Posts under iCloud & Data subtopic

Post

Replies

Boosts

Views

Activity

How to set Custom name an iCloud container?
In the application iCloud integration but in the container, it displays the name of the bundle that owns the container. Configuration iCloud capability from developer account Enable iCloud capability from Xcode Added keys and value into info.plist file as below <key>NSUbiquitousContainers</key> <dict> <key>iCloud.com.example.applepaydemo</key> <dict> <key>NSUbiquitousContainerName</key> <string>Apple Demo</string> <key>NSUbiquitousContainerIsDocumentScopePublic</key> <true/> <key>NSUbiquitousContainerSupportedFolderLevels</key> <string>Any</string> </dict> </dict> Issue: It displays applepaydemo name of container not Apple Demo in iCloud Manage Account Storage
1
0
1.1k
Aug ’23
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
8
1
8.9k
Aug ’23
#Predicate doesn't work with enum
Problem The following code doesn't work: let predicate = #Predicate<Car> { car in car.size == size //This doesn't work } Console Error Query encountered an error: SwiftData.SwiftDataError(_error: SwiftData.SwiftDataError._Error.unsupportedPredicate) Root cause Size is an enum, #Predicate works with other type such as String however doesn't work with enum Enum value is saved however is not filtered by #Predicate Environment Xcode: 15.0 (15A240d) - App Store macOS: 14.0 (23A339) - Release Candidate Steps to reproduce Run the app on iOS 17 or macOS Sonoma Press the Add button Notice that the list remains empty Expected behaviour List should show the newly created small car Actual behaviour List remains empty inspite of successfully creating the small car. Feedback FB13194334 Code Size enum Size: String, Codable { case small case medium case large } Car import SwiftData @Model class Car { let id: UUID let name: String let size: Size init( id: UUID, name: String, size: Size ) { self.id = id self.name = name self.size = size } } ContentView struct ContentView: View { var body: some View { NavigationStack { CarList(size: .small) } } CarList import SwiftUI import SwiftData struct CarList: View { let size: Size @Environment(\.modelContext) private var modelContext @Query private var cars: [Car] init(size: Size) { self.size = size let predicate = #Predicate<Car> { car in car.size == size //This doesn't work } _cars = Query(filter: predicate, sort: \.name) } var body: some View { List(cars) { car in VStack(alignment: .leading) { Text(car.name) Text("\(car.size.rawValue)") Text(car.id.uuidString) .font(.footnote) } } .toolbar { Button("Add") { createCar() } } } private func createCar() { let name = "aaa" let car = Car( id: UUID(), name: name, size: size ) modelContext.insert(car) } }
6
1
2.3k
Sep ’23
Custom struct Codable for SwiftData
I'm encountering an issue encoding/decoding a custom struct from SwiftData. As it's all happening behind the generated code of SwiftData and a decoder, I'm not really sure what's going on. I have a custom type defined kind of like this: public struct Group<Key: Hashable, Element: Hashable> { private var elementGroups: [Element: Key] private var groupedElements: [Key: [Element]] } In short, it allows multiple elements (usually a string), to be grouped, referenced by some key. I have Codable conformance to this object, so I can encode and decode it. For simplicity, the elementGroups is encoded/decoded, and the groupedElements is rebuilt when decoding. My implementation is similar to this: extension Group: Codable where Key: Codable, Element: Codable { private enum Keys: CodingKey { case groups } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: Keys.self) let decoded = try container.decode([Element: Key].self, forKey: .groups) // Enumerate over the element groups, and populate the list of elements. // var elements: [Key: [Element]] = [:] for group in decoded { elements[group.value] = (elements[group.value] ?? []) + [group.key] } elementGroups = decoded groupedElements = elements } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: Keys.self) try container.encode(elementGroups, forKey: .groups) } } This works fine when encoding and decoding to JSON, but when I attempt to use this structure as a value within a SwiftData model, then decoding the type crashes my application. @Model final class MyModel { var id: UUID = UUID() var groups: Group<UUID, String> = Group<UUID, String>() init(id: UUID) { self.id = id } } When something attempts to decode the groups property on the MyModel object, it crashes with the following error: Could not cast value of type 'Swift.Optional<Any>' (0x1ed7d0290) to 'Swift.Dictionary<Swift.String, Foundation.UUID>' (0x121fa9448). I would guess that there is a nil value stored for groups in SwiftData, and attempting to decode it to a Group<UUID, String> type is failing. It's odd that it doesn't throw an exception though, and hard crashes. Also, I'm not sure why it's optional, as a value is being written out. Does anyone have any ideas?
4
1
1.7k
Oct ’23
Selecting which data models sync with iCloud SwiftData
So I am trying to sync only some of my Models with iCloud and others kept locally in the default.store. I am having a world of issues so before I start looking for the needle in my haystack. I would like to ask this forum, is my approach one that should work as desired or is my code obviously why things are not working? All my Models have default values and relationships where needed are optionals. iCloud is working but my issue arises when i try to exclude some models. So I want to sync "modelsForCloudSyncing" but not "modelNotForCloudSyncing" In advance thank you var avoidCloudSyncModelContainer : ModelContainer = { let modelNotForCloudSyncing = Schema([NoCloudSyncModel.self]) let modelConfigForNoCloudSync = ModelConfiguration(schema: modelNotForCloudSyncing, cloudKitDatabase: .none) let modelsForCloudSyncing = Schema([CloudSyncModelA.self, CloudSyncModelB.self, CloudSyncModelC.self]) let modelConfigForCloudSync = ModelConfiguration(schema: modelsForCloudSyncing, cloudKitDatabase: .automatic) do { return try ModelContainer(for: NoCloudSyncModel.self, CloudSyncModelA.self, CloudSyncModelB.self, CloudSyncModelC.self, configurations: modelConfigForNoCloudSync, modelConfigForCloudSync) } catch { fatalError("Could not create ModelContainer: \(error)") } }() ... { ContentView() }.modelContainer(avoidCloudSyncModelContainer)
11
2
2.4k
Nov ’23
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.
2
0
826
Dec ’23
How to change from non-versioned to versioned schema in swiftdata?
My app is using SwiftData, but I deployed it to the app store with no VersionedSchema applied without thinking about migrating the model. Now I need to migrate the data and I need help from someone who has experience moving from non-versioned to versioned. Assuming I currently have a version1, version2 schema, it works fine for the initial install situation, but when I migrate to version1, version2 in an app that is listed on the app store, I run into problems. I don't have any logs to show for it. Thread 1: EXC_BAD_ACCESS (code=2, address=0x16a6578f0) If anyone has had the same experience as above, please respond, thanks! Let me know what kind of logs you need and I'll add them as a comment.
2
1
928
Dec ’23
.transformable with ValueTransformer failing after Xcode 15.1 update
In Xcode 15.0.1, I created a new project to start working with SwiftData. I did this by creating a default App project and checking the Use SwiftData checkbox. The resulting project contains just three files: an app entry point file, a ContentView SwiftUI view file, and an Item model file. The only change I made was to annotate the default Item timestamp property with a .transformable attribute. Here is the resulting model: @Model final class Item { @Attribute(.transformable(by: TestVT.self)) var timestamp: Date // Only updated this line init(timestamp: Date) { self.timestamp = timestamp } } And here is the definition of TestVT. It is a basic ValueTransformer that simply tries to store the Date as a NSNumber: // Added this class TestVT: ValueTransformer { static let name = NSValueTransformerName("TestVT") override class func transformedValueClass() -> AnyClass { NSNumber.self } override class func allowsReverseTransformation() -> Bool { true } override func transformedValue(_ value: Any?) -> Any? { guard let date = value as? Date else { return nil } let ti = date.timeIntervalSince1970 return NSNumber(value: ti) } override func reverseTransformedValue(_ value: Any?) -> Any? { guard let num = value as? NSNumber else { return nil } let ti = num.doubleValue as TimeInterval return Date(timeIntervalSince1970: ti) } } And finally, I made sure to register my ValueTransformer but updating the sharedModelContainer definition in the App: var sharedModelContainer: ModelContainer = { ValueTransformer.setValueTransformer(TestVT(), forName: TestVT.name) // Only added this line let schema = Schema([ Item.self, ]) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) do { return try ModelContainer(for: schema, configurations: [modelConfiguration]) } catch { fatalError("Could not create ModelContainer: \(error)") } }() Prior to Xcode 15.1, this was working fine. However, now when I try to create an item when running the app I get the following error: Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for attribute: property = "timestamp"; desired type = NSNumber; given type = __NSTaggedDate; value = 2023-12-14 01:47:11 +0000.' I'm unsure of why this stopped working. The error seems to be complaining about the input being of type Date when NSNumber was expected, but I thought that's what the ValueTransformer was supposed to be doing. Important note: prior to Xcode 15.1, I did not originally override the transformedValueClass() and everything was working but in the new Xcode when launching the app I was getting a Thread 1: EXC_BAD_ACCESS (code=1, address=0x0) on the return try ModelContainer(...) line. Removing the .transformable property from my model fixed the issue. That's why I added the override here, because I think the docs indicate overriding it as well and I missed that the first time. This being said, I think the code I have is what a correct ValueTransformer would look like. If anyone has experienced this issue, or has a working ValueTransformer for SwiftData in Xcode 15.1, please let me know. Appreciate any help with this issue. Thanks so much!
4
3
1.7k
Dec ’23
SwiftData query filter on an @State var
Hey, I might have a super dumb question but I am very new to SwiftData and Swift in general. So I have the following code in a View: @State private var selectedMonth: String = "" @State private var selectedYear: String = "" @Query( filter: #Predicate<Transaction> { transaction in transaction.date.monthString == selectedMonth && transaction.date.yearString == selectedYear }, sort: \Transaction.date ) var transactions: [Transaction] My @State vars selectedMonth and selectedYear get changed whenever the user selects options from a menu. I've had trouble getting this query/filter to work, does anyone know if filtering on dynamic (@State) variables is supported in SwiftData? If so, am I missing something here? I've also tried the pattern of having the transactions array live elsewhere in a regular swift file with the @Published macro and calling an update function whenever the @State vars selectedMonth or selectedYear get updated using .onChange(of:) . Unfortunately, I've not been able to find any documentation or examples about setting up a regular swift file (not SwiftUI) to work with SwiftData (e.g. querying from it, accessing the context/container, etc) Would appreciate anyone giving tips or pointing me in the right direction. Sorry if its a noob questions, thanks so much for any help
2
4
4.4k
Dec ’23
How to add multiple ModelConfigurations to a ModelContainer?
Anyone successfully able to add two or more ModelConfigurations to a ModelContainer? DESCRIPTION OF PROBLEM: When you create two ModelConfigurations for two different models and combine them into one ModelContainer, it seems the @Query fails to find the models. Crashes app with error: “Thread 1: "NSFetchRequest could not locate an NSEntityDescription for entity name 'NumberModel'" STEPS TO REPRODUCE 1 - Create a new iOS project. 2 - In ContentView.swift add this code: import SwiftData import SwiftUI struct ContentView: View { @Query private var colors: [ColorModel] @Query private var numbers: [NumberModel] var body: some View { List { ForEach(colors) { color in Text(color.name) } ForEach(numbers) { number in Text(number.name) } } } } #Preview { ContentView() .modelContainer(for: [ColorModel.self, NumberModel.self]) } 3 - In App file, add this code: import SwiftData import SwiftUI @Model class ColorModel { var name: String = "" init(name: String) { self.name = name } } @Model class NumberModel { var name: String = "" init(name: String) { self.name = name } } @main struct MultipleModelConfigsApp: App { private var container: ModelContainer init() { do { let config1 = ModelConfiguration(for: ColorModel.self) let config2 = ModelConfiguration(for: NumberModel.self) let container = try ModelContainer( for: ColorModel.self, NumberModel.self, configurations: config1, config2 ) self.container = container } catch { fatalError("ModelContainer creation failed.") } } var body: some Scene { WindowGroup { ContentView() .modelContainer(container) } } } 4 - Now run the app and observe the crash and the error stated above. VERSION OF XCODE Version 15.1 (15C65) FEEDBACK REPORT FB: FB13504577 (Xcode project attached to FB)
4
2
1.5k
Dec ’23
SwiftData with CloudKit failing to migrate schema
My app has been in the App Store a few months. In that time I've added a few updates to my SwiftData schema using a MigrationPlan, and things were seemingly going ok. But then I decided to add CloudKit syncing. I needed to modify my models to be compatible. So, I added another migration stage for it, changed the properties as needed (making things optional or adding default values, etc.). In my tests, everything seemed to work smoothly updating from the previous version to the new version with CloudKit. So I released it to my users. But, that's when I started to see the crashes and error reports come in. I think I've narrowed it down to when users update from older versions of the app. I was finally able to reproduce this on my end, and Core Data is throwing an error when loading the ModelContainer saying "CloudKit integration requires that all attributes be optional, or have a default value set." Even though I did this in the latest schema. It’s like it’s trying to load CloudKit before performing the schema migration, and since it can’t, it just fails and won’t load anything. I’m kinda at a loss how to recover from this for these users other than tell them to delete their app and restart, but obviously they’ll lose their data that way. The only other idea I have is to setup some older builds on TestFlight and direct them to update to those first, then update to the newest production version and hope that solves it. Any other ideas? And what can I do to prevent this for future users who maybe reinstall the app from an older version too? There's nothing special about my code for loading the ModelContainer. Just a basic: let container = try ModelContainer( for: Foo.self, Bar.self, migrationPlan: SchemaMigration.self, configurations: ModelConfiguration(cloudKitDatabase: .automatic) )
7
3
3k
Jan ’24
How to get notified on CKError.quotaExceeded
Hi all, I have an iOS app which uses CloudKit and the standard NSPersistentCloudKitContainer, which I rely on for syncing app data between the user's devices. If the user's iCloud account is full I can see a log message while debugging in Xcode shortly after startup which looks something like this: error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _requestAbortedNotInitialized:](2183): <NSCloudKitMirroringDelegate: 0x281ddc1e0> - Never successfully initialized and cannot execute request '<NSCloudKitMirroringExportRequest: 0x2841e00f0> 51383346-87BA-44D8-B527-A0B1EE35A0EF' due to error: <CKError 0x282c50db0: "Partial Failure" (2/1011); "Failed to modify some records"; uuid = 7BA17495-4F05-4AF4-A463-C0DF5A823B2E; container ID = "iCloud.com.neufsters.pangram"; partial errors: { E30B2972-FD4B-4D2A-BD1C-EB6F33F5367D:(com.apple.coredata.cloudkit.zone:__defaultOwner__) = <CKError 0x282c155f0: "Quota Exceeded" (25/2035); server message = "Quota exceeded"; op = FC4D3188D0A46ABC; uuid = 7BA17495-4F05-4AF4-A463-C0DF5A823B2E; Retry after 315.0 seconds> 2FC9A487-D630-444D-B7F4-27A0F3A6B46E:(com.apple.coredata.cloudkit.zone:__defaultOwner__) = <CKError 0x282c52820: "Quota Exceeded" (25/2035); server message = "Quota exceeded"; op = FC4D3188D0A46ABC; uuid = 7BA17495-4F05-4AF4-A463-C0DF5A823B2E; Retry after 315.0 seconds> 903DD6A0-0BD8-46C0-84FB-E89797514D9F:(com.apple.coredata.cloudkit.zone:__defaultOwner__) = <CKError 0x282c513e0: "Quota Exceeded" (25/2035); server message = "Quota exceeded"; op = FC4D3188D0A46ABC; uuid = 7BA17495-4F05-4AF4-A463-C0DF5A823B2E; Retry after 315.0 seconds> }> I would like to know how I can get a callback of some sort so I can run code if this CloudKit/CoreData error happens. In particular I'd like to put up some sort of warning to the user letting them know their data isn't going to sync. Please note that I'm not looking for how to do error handling as a result of a user-initiated CloudKit API call. I'm looking for how to get notified when the background syncing logs errors like the above. Thanks, Russ
5
3
1.2k
Jan ’24
Core Data migration then run old app version
I'm sorta baffled right now. I am trying to wonder how I might detect a updated SQL Store in an older app. have a baseline app, and create a SQL-based repository in an updated app, change the model and verify that you can see the updated model version. Using lightweight migration re-run the older app (which will inherit the newer SQL repository). YIKES - no error when creating the NSPersistenStoreCoordinator! Nothing in the metadata to imply the store is newer than the model: [_persistentStoreCoordinator metadataForPersistentStore:store] My question: is there any way to detect this condition? David
1
0
657
Feb ’24
CKSyncEngine.RecordZoneChangeBatch and the CKSyncEngineDelegate protocol
I'm having some trouble with the following function from the CKSyncEngineDelegate protocol. func nextRecordZoneChangeBatch(_ context: CKSyncEngine.SendChangesContext, syncEngine: CKSyncEngine) async -> CKSyncEngine.RecordZoneChangeBatch? { The sample code from the documentation is func nextRecordZoneChangeBatch( _ context: CKSyncEngine.SendChangesContext, syncEngine: CKSyncEngine ) async -> CKSyncEngine.RecordZoneChangeBatch? { // Get the pending record changes and filter by the context's scope. let pendingChanges = syncEngine.state.pendingRecordZoneChanges .filter { context.options.zoneIDs.contains($0) } // Return a change batch that contains the corresponding materialized records. return await CKSyncEngine.RecordZoneChangeBatch( pendingChanges: pendingChanges) { self.recordFor(id: $0) } } init?(pendingChanges: [CKSyncEngine.PendingRecordZoneChange], recordProvider: (CKRecord.ID) -> (CKRecord?)) works fine for the sample app which only has one record type, but it seems incredible inefficient for my app which has a dozen different record types. The recordProvider gives you a CKRecord.ID, but not the CKRecord.RecordType. Searching each record type for a matching ID seems very inefficient. Doesn't the CKSyncEngine.PendingRecordZoneChange contain an array of CKRecords, not just CKRecord.IDs? According to the documentation CKSyncEngine.RecordZoneChangeBatch has a recordsToSave property, but Xcode reports 'CKSyncEngine.PendingRecordZoneChange' has no member 'recordsToSave' I'm looking for someway to get the CKRecords from syncEngine.state.pendingRecordZoneChanges.
3
1
735
Feb ’24
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
1
1.1k
Mar ’24
Troubleshooting Core Data Lightweight Migration: A Real-World Challenge
In my recent endeavor, I aimed to introduce new Fetch Index Elements to the Core Data model of my iOS application. To achieve this, I followed a process of lightweight migration, detailed as follows: Navigate to Editor > Add Model Version to create a new version of the data model. Name the new version with a sequential identifier (e.g., MyAppModelV3.xcdatamodel) based on the naming convention of previous models. Select the newly created version, MyAppModelV3.xcdatamodel, as the active model. Mark this new version as the "Current" model in the Xcode properties panel on the right. In the new version of the model, MyAppModelV3.xcdatamodel, and add the new Fetch Index Elements there. Also, insert "v3" in the Versioning Hash Modifier field of affected entity, to indicate this modification. Upon reflection, I realized that creating a new version of the xcdatamodel might not have been necessary for this particular case. However, it appears to have caused no adverse effects on the application's functionality. During testing, I executed the application in a simulated environment, initially running an older version of the app to inspect the database content with SQLite DB Browser. I then upgraded to the latest app version to verify that the migration was successfully completed without causing any crashes. Throughout this testing phase, I employed the -com.apple.CoreData.MigrationDebug 1 flag to monitor all SQL operations, ensuring that indexes were appropriately dropped and recreated for the affected entity. Following thorough testing, I deployed the update to production. The majority of users were able to upgrade to the new app version seamlessly. However, a small fraction reported crashes at startup, indicated by the following error message: Fatal error: Unresolved error Error Domain=NSCocoaErrorDomain Code=134110 "An error occurred during persistent store migration." UserInfo={NSUnderlyingError=0x2820ad3e0 {Error Domain=NSCocoaErrorDomain Code=134100 "The managed object model version used to open the persistent store is incompatible with the one that was used to create the persistent store." UserInfo={metadata={ NSPersistenceFrameworkVersion = 1338; NSStoreModelVersionChecksumKey = "qcPf6+DfpsPrDQ3j1EVXcBIrFe1O0R6IKd30sJf4IrI="; NSStoreModelVersionHashes = { NSAttachment = {length = 32, ... Strangely, the only way I could replicate this issue in the simulator was by running the latest version of the app followed by reverting to an older version, a scenario unlikely to occur in a real-world setting. This raises the question: How could this situation arise with actual users, considering they would typically move from an old to a new version rather than the reverse? I am reaching out to the community for insights or advice on this matter. Has anyone else encountered a similar problem during the Core Data migration process? How did you resolve it?
1
1
900
Mar ’24