Environment
visionOS 26
Xcode 26
Issue
I am experiencing crash when trying to access a [String] from a @Model data, after dismissing an immersiveSpace and opening a WindowGroup.
This crash only occurs when trying to access the [String] property of my Model. It works fine with other properties.
Thread 1: Fatal error: This backing data was detached from a context without resolving attribute faults: PersistentIdentifier(...)
Steps to Reproduce
Open WindowGroup
Dismiss window, open ImmersiveSpace
Dismiss ImmersiveSpace, reopen WindowGroup
Any guidance would be appreciated!
@main
struct MyApp: App {
var body: some Scene {
WindowGroup(id: "main") {
ContentView()
}
.modelContainer(for: [Item.self])
ImmersiveSpace(id: "immersive") {
ImmersiveView()
}
}
}
// In SwiftData model
@Model
class Item {
var title: String = "" // Accessing this property works fine
var tags: [String] = []
@storageRestrictions(accesses: _$backingData, initializes: _tags)
init(initialValue) {
_$backingData.setValue(forKey: \. tags, to: initialValue)
_tags =_ SwiftDataNoType()
}
get {
_$observationRegistrar.access(self, keyPath: \.tags)
**return self getValue(forkey: \.tags)** // Crashes here
}
iCloud & Data
RSS for tagLearn how to integrate your app with iCloud and data frameworks for effective data storage
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I would like to have a SwiftData predicate that filters against an array of PersistentIdentifiers.
A trivial use case could filtering Posts by one or more Categories. This sounds like something that must be trivial to do.
When doing the following, however:
let categoryIds: [PersistentIdentifier] = categoryFilter.map { $0.id }
let pred = #Predicate<Post> {
if let catId = $0.category?.persistentModelID {
return categoryIds.contains(catId)
} else {
return false
}
}
The code compiles, but produces the following runtime exception (XCode 26 beta, iOS 26 simulator):
'NSInvalidArgumentException', reason: 'unimplemented SQL generation for predicate : (TERNARY(item != nil, item, nil) IN {}) (bad LHS)'
Strangely, the same code works if the array to filter against is an array of a primitive type, e.g. String or Int.
What is going wrong here and what could be a possible workaround?
Hi all,
In my SwiftUI / SwiftData / Cloudkit app which is a series of lists, I have a model object called Project which contains an array of model objects called subprojects:
final class Project1
{
var name: String = ""
@Relationship(deleteRule: .cascade, inverse: \Subproject.project) var subprojects : [Subproject]?
init(name: String)
{
self.name = name
self.subprojects = []
}
}
The user will select a project from a list, which will generate a list of subprojects in another list, and if they select a subproject, it will generate a list categories and if the user selects a category it will generate another list of child objects owned by category and on and on.
This is the pattern in my app, I'm constantly passing arrays of model objects that are the children of other model objects throughout the program, and I need the user to be able to add and remove things from them.
My initial approach was to pass these arrays as bindings so that I'd be able to mutate them. This worked for the most part but there were two problems: it was a lot of custom binding code and when I had to unwrap these bindings using init?(_ base: Binding<Value?>), my program would crash if one of these arrays became nil (it's some weird quirk of that init that I don't understand at al).
As I'm still learning the framework, I had not realized that the @model macro had automatically made my model objects observable, so I decided to remove the bindings and simply pass the arrays by reference, and while it seems these references will carry the most up to date version of the array, you cannot mutate them unless you have access to the parent and mutate it like such:
project.subcategories?.removeAll { $0 == subcategory }
project.subcategories?.append(subcategory)
This is weirding me out because you can't unwrap subcategories before you try to mutate the array, it has to be done like above. In my code, I like to unwrap all optionals at the moment that I need the values stored in them and if not, I like to post an error to the user. Isn't that the point of optionals? So I don't understand why it's like this and ultimately am wondering if I'm using the correct design pattern for what I'm trying to accomplish or if I'm missing something? Any input would be much appreciated!
Also, I do have a small MRE project if the explanation above wasn't clear enough, but I was unable to paste in here (too long), attach the zip or paste a link to Google Drive. Open to sharing it if anyone can tell me the best way to do so. Thanks!
I have made a Swift App for MacOS 15 under XCode 16.3, which runs fine. I also want to run it under the previous MacOS 14. Unfortunately it crashes without even starting up (it does not even reach the first log output statement on the first view)
The crash reason is
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Exception Type: EXC_BAD_INSTRUCTION (SIGILL)
Exception Codes: 0x0000000000000001, 0x0000000000000000
Termination Reason: Namespace SIGNAL, Code 4 Illegal instruction: 4
Terminating Process: exc handler [2970]
I have set the miminium deployment to MacOS 14.0 but to no effect. The XCode machine is a MacOS 15.4 on Arm M3 and the target machine is MacOS 14.7.5 on Intel (MacBook Air)
I think it might be related to the compiler and linker settings.
Greetings my fellow engineers,
I use SwiftData in my iOS app. The schema is unversioned and consists of a single model. I've been modifying the model for almost two years now and relying on automatic database migrations. I had no problems for all that time, but now trying to add a property to the model or even remove a property from the model results in an error which seems like SwiftData is no longer capable of performing an automatic migration.
The log console has things like the following:
CoreData: error: NSUnderlyingError : Error Domain=NSCocoaErrorDomain Code=134190 "(null)" UserInfo={reason=Each property must have a unique renaming identifier}
CoreData: error: reason : Can't find or automatically infer mapping model for migration
CoreData: error: storeType: SQLite
CoreData: error: configuration: default
CoreData: annotation: options:
CoreData: annotation: NSMigratePersistentStoresAutomaticallyOption : 1
CoreData: annotation: NSInferMappingModelAutomaticallyOption : 1
CoreData: annotation: NSPersistentStoreRemoteChangeNotificationOptionKey : 1
CoreData: annotation: NSPersistentHistoryTrackingKey : 1
CoreData: error: <NSPersistentStoreCoordinator: 0x7547b5480>: Attempting recovery from error encountered during addPersistentStore: 0x753f8d800 Error Domain=NSCocoaErrorDomain Code=134140 "Persistent store migration failed, missing mapping model."
Have you ever encountered such an issue? What are my options?
I'm experiencing a critical issue with SwiftData custom migrations where objects created during migration appear to be inserted successfully but aren't persisted or found by queries after migration completes. The migration logs show objects being created, but subsequent queries return zero results.
I'm migrating from schema version V2 to V2_5, which involves:
Renaming Person class to GroupData
Keeping the same data structure but changing the class name while keeping the old class.
Using a custom migration stage to copy data from old to new schema
Below is an extract of my two schema and migration plan:
Environment:
Xcode 16.0,
iOS 18.0,
Swift 6.0
SchemaV2
enum LinkMapV2: VersionedSchema {
static let versionIdentifier: Schema.Version = .init(2, 0, 0)
static var models: [any PersistentModel.Type] {
[AnnotationData.self, Person.self, History.self]
}
@Model
final class Person {
@Attribute(.unique) var id: UUID
var name: String
var photo: String
var requirement: String
var statue: Bool
var annotationId: UUID?
var number: Int = 0
init(id: UUID = UUID(), name: String = "", photo: String = "", requirement: String = "", status: Bool = false, annotationId: UUID? = nil, number: Int = 0) {
self.id = id
self.name = name
self.photo = photo
self.requirement = requirement
self.statue = status
self.annotationId = annotationId
self.number = number
}
}
}
Schema V2_5
static let versionIdentifier: Schema.Version = .init(2, 5, 0)
static var models: [any PersistentModel.Type] {
[AnnotationData.self, Person.self, GroupData.self, History.self]
}
// Keep the old Person model for migration
@Model
final class Person {
@Attribute(.unique) var id: UUID
var name: String
var photo: String
var requirement: String
var statue: Bool
var annotationId: UUID?
var number: Int = 0
init(id: UUID = UUID(), name: String = "", photo: String = "", requirement: String = "", status: Bool = false, annotationId: UUID? = nil, number: Int = 0) {
self.id = id
self.name = name
self.photo = photo
self.requirement = requirement
self.statue = status
self.annotationId = annotationId
self.number = number
}
}
// Add the new GroupData model that mirrors Person
@Model
final class GroupData {
@Attribute(.unique) var id: UUID
var name: String
var photo: String
var requirement: String
var status: Bool
var annotationId: UUID?
var number: Int = 0
init(id: UUID = UUID(), name: String = "", photo: String = "", requirement: String = "", status: Bool = false, annotationId: UUID? = nil, number: Int = 0) {
self.id = id
self.name = name
self.photo = photo
self.requirement = requirement
self.status = status
self.annotationId = annotationId
self.number = number
}
}
}
Migration Plan
static let migrationV2toV2_5 = MigrationStage.custom(
fromVersion: LinkMapV2.self,
toVersion: LinkMapV2_5.self,
willMigrate: { context in
do {
let persons = try context.fetch(FetchDescriptor<LinkMapV2.Person>())
print("=== MIGRATION STARTED ===")
print("Found \(persons.count) Person objects to migrate")
guard !persons.isEmpty else {
print("No Person data requires migration")
return
}
for person in persons {
print("Migrating Person: '\(person.name)' with ID: \(person.id)")
let newGroup = LinkMapV2_5.GroupData(
id: person.id, // Keep the same ID
name: person.name,
photo: person.photo,
requirement: person.requirement,
status: person.statue,
annotationId: person.annotationId,
number: person.number
)
context.insert(newGroup)
print("Inserted new GroupData: '\(newGroup.name)'")
// Don't delete the old Person yet to avoid issues
// context.delete(person)
}
try context.save()
print("=== MIGRATION COMPLETED ===")
print("Successfully migrated \(persons.count) Person objects to GroupData")
} catch {
print("=== MIGRATION ERROR ===")
print("Migration failed with error: \(error)")
}
},
didMigrate: { context in
do {
// Verify migration in didMigrate phase
let groups = try context.fetch(FetchDescriptor<LinkMapV2_5.GroupData>())
let oldPersons = try context.fetch(FetchDescriptor<LinkMapV2_5.Person>())
print("=== MIGRATION VERIFICATION ===")
print("New GroupData count: \(groups.count)")
print("Remaining Person count: \(oldPersons.count)")
// Now delete the old Person objects
for person in oldPersons {
context.delete(person)
}
if !oldPersons.isEmpty {
try context.save()
print("Cleaned up \(oldPersons.count) old Person objects")
}
// Print all migrated groups for debugging
for group in groups {
print("Migrated Group: '\(group.name)', Status: \(group.status), Number: \(group.number)")
}
} catch {
print("Migration verification error: \(error)")
}
}
)
And I've attached console output below:
Console Output
I have a document based SwiftData app in which I would like to implement a persistent cache. For obvious reasons, I would not like to store the contents of the cache in the documents themselves, but in my app's data directory.
Is a use case, in which a document based SwiftData app uses not only the ModelContainers from the currently open files, but also a ModelContainer writing a database file in the app's documents directory (for cache, settings, etc.) supported?
If yes, how can you inject two different ModelContexts, one tied to the currently open file and one tied to the local database, into a SwiftUI view?
In a document based SwiftData app for macOS, how do you go about opening a (modal) child window connected to the ModelContainer of the currently open document?
Using .sheet() does not really result in a good UX, as the appearing view lacks the standard window toolbar.
Using a separate WindowGroup with an argument would achieve the desired UX. However, as WindowGroup arguments need to be Hashable and Codable, there is no way to pass a ModelContainer or a ModelContext there:
WindowGroup(id: "myWindowGroup", for: MyWindowGroupArguments.self) { $args in
ViewThatOpensInAWindow(args: args)
}
Is there any other way?
Hello,
I'm trying to work on an iPadOS and macOS app that will rely on the document-based system to create some kind of orientation task to follow.
Let say task1.myfile will be a check point regulation from NYC to SF and task2.myfile will be a visit as many key location as you can in SF.
The file represent the specific landmark location and rules of the game.
And once open, I will be able to read KML/GPS file to evaluate their score based with the current task.
But opened GPS files does not have to be stored in the task file itself, it stay alongside.
I wanted to use that scenario to experiment with SwiftData (I'm a long time CoreData user, I even wrote my own WebDAV based persistent store back in the day), and so, mix both on file and in memory persistent store, with distribution based on object class.
With CoreData it would have been possible, but I do not see how to achieve that with SwiftData and DocumentGroup integration.
Any idea how to do that?
The stuff I've found by searching has confused me, so hopefully someone can help simplify it for me?
I have an app (I use it for logging which books I've given away), and I could either add a bunch of things to the app, or I could have another app (possibly a CLI tool) to generate some reports I'd like.
I have a CoreData model with two configuration - but several problems. Notably the viewContext only shows data from the .private configuration. Here is the setup:
The private configuration holds entities, for example, User and Course and the shared one holds entities, for example, Player and League. I setup the NSPersistentStoreDescriptions to use the same container but with a databaseScope of .private/.shared and with the configuration of "Private"/"Shared". loadPersistentStores() does not report an error.
If I try container.initializeCloudKitSchema() only the .private configuration produces CKRecord types. If I create a companion app using one configuration (w/ all entities) the schema initialization creates all CKRecord types AND I can populate some data in the .private and a created CKShare. I see that data in the CloudKit dashboard.
If I axe the companion app and run the real thing w/ two configurations, the viewContext only has the .private data. Why?
If when querying history I use NSPersistentHistoryTransaction.fetchRequest I get a nil return when using two configurations (but non-nil when using one).
Hi all, I've contacted Apple about this privately but I wanted to post this publicly too just to see if anyone else is experiencing the same issue. We use CloudKit to store "documents" (we'll call them) for our users. We use it directly, not via CoreData etc but through the lower level APIs.
This has been working great for the last 9 months or so. Since a few days ago we've started receiving reports from users that their data has disappeared without a trace from their app. Obviously this is very serious and severe for us. We keep a local copy of the users data but if CloudKit tells us this data has been deleted we remove that local copy to keep in sync.
Nothing has changed client side in terms of our code, and the only way we can see that could cause this, is a fetch that we perform asking for a list of the users "documents" is returning no rows/results, or possibly returning rows with invalid or missing fields.
We have about 30,000 active users per day (1.5m requests/day) using CloudKit and we have only a handful of reports of this. Again this only started happening this week after 9 months of good service.
Has anyone else noticed anything "strange" lately, fetches returning empty? fields missing? Is anyone at Apple aware of any recent changes to CloudKit? or outages? We're really unsure how or who should handle this and who we can escalate to? Any help appreciated.
We have a workaround/mitigation on the way through review at the moment but this is a really big problem for us if we can't rely on CloudKit to remember users data reliably.
I'm looking for guidance how to mitigate this crash. It seems super deep inside Core Data' FRC fetchedObjects management.
In my code, it's initiated by this
viewContext.perform {
[unowned self] in
self.viewContext.mergeChanges(fromContextDidSave: notification)
}
which is directly followed by the stack trace below.
Basically merging data from .NSManagedObjectContextDidSave notification from another NSManagedObjectContext. Nothing special, it works great for years, apart from these rare occurrences.
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Reason: -[__NSCFArray objectAtIndex:]: index (235) beyond bounds (234)
Termination Reason: SIGNAL 6 Abort trap: 6
Triggered by Thread: 0
Last Exception Backtrace:
0 CoreFoundation 0x199e947cc __exceptionPreprocess + 164 (NSException.m:249)
1 libobjc.A.dylib 0x1971672e4 objc_exception_throw + 88 (objc-exception.mm:356)
2 CoreFoundation 0x199fc4258 _NSArrayRaiseBoundException + 368 (NSCFArray.m:22)
3 CoreFoundation 0x199e288a4 -[__NSCFArray objectAtIndex:] + 200 (NSCFArray.m:42)
4 CoreData 0x1a1e17338 -[_PFMutableProxyArray objectAtIndex:] + 40 (_PFArray.m:1860)
5 CoreData 0x1a1e1673c -[NSFetchedResultsController _updateFetchedObjectsWithInsertChange:] + 380 (NSFetchedResultsController.m:1582)
6 CoreData 0x1a1e1426c __82-[NSFetchedResultsController(PrivateMethods) _core_managedObjectContextDidChange:]_block_invoke + 2240 (NSFetchedResultsController.m:2171)
7 CoreData 0x1a1dcdf80 developerSubmittedBlockToNSManagedObjectContextPerform + 156 (NSManagedObjectContext.m:4002)
8 CoreData 0x1a1e41a44 -[NSManagedObjectContext performBlockAndWait:] + 216 (NSManagedObjectContext.m:4113)
9 CoreData 0x1a1e41034 -[NSFetchedResultsController _core_managedObjectContextDidChange:] + 124 (NSFetchedResultsController.m:2379)
10 CoreFoundation 0x199e632f4 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 148 (CFNotificationCenter.c:701)
11 CoreFoundation 0x199e63210 ___CFXRegistrationPost_block_invoke + 88 (CFNotificationCenter.c:194)
12 CoreFoundation 0x199e63158 _CFXRegistrationPost + 436 (CFNotificationCenter.c:222)
13 CoreFoundation 0x199e6170c _CFXNotificationPost + 728 (CFNotificationCenter.c:1248)
14 Foundation 0x198a84ea4 -[NSNotificationCenter postNotificationName:object:userInfo:] + 92 (NSNotification.m:531)
15 CoreData 0x1a1e11650 -[NSManagedObjectContext _createAndPostChangeNotification:deletions:updates:refreshes:deferrals:wasMerge:] + 1736 (NSManagedObjectContext.m:8098)
16 CoreData 0x1a1e10e0c -[NSManagedObjectContext _postRefreshedObjectsNotificationAndClearList] + 164 (NSManagedObjectContext.m:7631)
17 CoreData 0x1a1e0fad8 -[NSManagedObjectContext _processRecentChanges:] + 100 (NSManagedObjectContext.m:7714)
18 CoreData 0x1a1e3563c -[NSManagedObjectContext _coreMergeChangesFromDidSaveDictionary:usingObjectIDs:withClientQueryGeneration:] + 3436 (NSManagedObjectContext.m:3723)
19 CoreData 0x1a1e34350 __116+[NSManagedObjectContext(_NSCoreDataSPI) _mergeChangesFromRemoteContextSave:intoContexts:withClientQueryGeneration:]_block_invoke_4 + 76 (NSManagedObjectContext.m:9531)
20 CoreData 0x1a1dcdf80 developerSubmittedBlockToNSManagedObjectContextPerform + 156 (NSManagedObjectContext.m:4002)
21 CoreData 0x1a1e41a44 -[NSManagedObjectContext performBlockAndWait:] + 216 (NSManagedObjectContext.m:4113)
22 CoreData 0x1a1e39880 +[NSManagedObjectContext _mergeChangesFromRemoteContextSave:intoContexts:withClientQueryGeneration:] + 2372 (NSManagedObjectContext.m:9537)
23 CoreData 0x1a1e344a0 -[NSManagedObjectContext mergeChangesFromContextDidSaveNotification:] + 292 (NSManagedObjectContext.m:0)
Topic:
App & System Services
SubTopic:
iCloud & Data
When deleting a SwiftData entity, I sometimes encounter the following error in a document based SwiftUI app:
Fatal error: Unexpected backing data for snapshot creation: SwiftData._FullFutureBackingData<MyEntityClass>
The deletion happens in a SwiftUI View and the code used to retrieve the entity is standard (the ModelContext is injected from the @Environment):
let myEntity = modelContext.model(for: entityIdToDelete)
modelContext.delete(myEntity)
Unfortunately, I haven't yet managed to isolate this any further in order to come up with a reproducible PoC.
Could you give me further information about what this error means?
Hi !
Would anyone know (if possible) how to create backup files to export and then import from the data recorded by SwiftData?
For those who wish, here is a more detailed explanation of my case:
I am developing a small management software with customers and events represented by distinct classes. I would like to have an "Export" button to create a file with all the instances of these 2 classes and another "Import" button to replace all the old data with the new ones from a previously exported file.
I looked for several solutions but I'm a little lost...
I'm writing some tests to confirm the behavior of my app. White creating a model actor to delete objects I realized that ModelContext.model(for:) does return objects that are deleted. I was able to reproduces this with this minimal test case:
@Model class Activity {
init() {}
}
struct MyLibraryTests {
let modelContainer = try! ModelContainer(
for: Activity.self,
configurations: ModelConfiguration(
isStoredInMemoryOnly: true
)
)
init() throws {
let context = ModelContext(modelContainer)
context.insert(Activity())
try context.save()
}
@Test func modelForIdAfterDelete() async throws {
let context = ModelContext(modelContainer)
let id = try context.fetch(FetchDescriptor<Activity>()).first!.id
context.delete(context.model(for: id) as! Activity)
try context.save()
let result = context.model(for: id) as? Activity
#expect(result == nil) // Expectation failed: (result → MyLibrary.Activity) == nil
}
@Test func fetchDescriptorAfterDelete() async throws {
let context = ModelContext(modelContainer)
let id = try context.fetch(FetchDescriptor<Activity>()).first!.id
context.delete(context.model(for: id) as! Activity)
try context.save()
let result = try context.fetch(
FetchDescriptor<Activity>(predicate: #Predicate { $0.id == id })
).first
#expect(result == nil)
}
}
Here I create a new context, insert an model and save it.
The test modelForIdAfterDelete does fail, as result still contains the deleted object.
I also tried to check #expect(result!.isDeleted), but it is also false.
With the second test I use a FetchDescriptor to retrieve the object by ID and it correctly returns nil.
Shouldn't both methods use a consistent behavior?
SwiftData crashes 100% when fetching history of a model that contains an optional codable property that's updated:
SwiftData/Schema.swift:389: Fatal error: Failed to materialize a keypath for someCodableID.someID from CrashModel. It is possible that this path traverses a type that does not work with append(), please file a bug report with a test.
Would really appreciate some help or even a workaround.
Code:
import Foundation
import SwiftData
import Testing
struct VaultsSwiftDataKnownIssuesTests {
@Test
func testCodableCrashInHistoryFetch() async throws {
let container = try ModelContainer(
for: CrashModel.self,
configurations: .init(
isStoredInMemoryOnly: true
)
)
let context = ModelContext(container)
try SimpleHistoryChecker.hasLocalHistoryChanges(context: context)
// 1: insert a new value and save
let model = CrashModel()
model.someCodableID = SomeCodableID(someID: "testid1")
context.insert(model)
try context.save()
// 2: check history it's fine.
try SimpleHistoryChecker.hasLocalHistoryChanges(context: context)
// 3: update the inserted value before then save
model.someCodableID = SomeCodableID(someID: "testid2")
try context.save()
// The next check will always crash on fetchHistory with this error:
/*
SwiftData/Schema.swift:389: Fatal error: Failed to materialize a keypath for someCodableID.someID from CrashModel. It is possible that this path traverses a type that does not work with append(), please file a bug report with a test.
*/
try SimpleHistoryChecker.hasLocalHistoryChanges(context: context)
}
}
@Model final class CrashModel {
// optional codable crashes.
var someCodableID: SomeCodableID?
// these actually work:
//var someCodableID: SomeCodableID
//var someCodableID: [SomeCodableID]
init() {}
}
public struct SomeCodableID: Codable {
public let someID: String
}
final class SimpleHistoryChecker {
static func hasLocalHistoryChanges(context: ModelContext) throws {
let descriptor = HistoryDescriptor<DefaultHistoryTransaction>()
let history = try context.fetchHistory(descriptor)
guard let last = history.last else {
return
}
print(last)
}
}
I am trying to add a custom JSON DataStore and DataStoreConfiguration for SwiftData. Apple kindly provided some sample code in the WWDC24 session, "Create a custom data store with SwiftData", and (once updated for API changes since WWDC) that works fine.
However, when I try to add a relationship between two classes, it fails. Has anyone successfully made a JSONDataStore with a relationship?
Here's my code; firstly the cleaned up code from the WWDC session:
import SwiftData
final class JSONStoreConfiguration: DataStoreConfiguration {
typealias Store = JSONStore
var name: String
var schema: Schema?
var fileURL: URL
init(name: String, schema: Schema? = nil, fileURL: URL) {
self.name = name
self.schema = schema
self.fileURL = fileURL
}
static func == (lhs: JSONStoreConfiguration, rhs: JSONStoreConfiguration) -> Bool {
return lhs.name == rhs.name
}
func hash(into hasher: inout Hasher) {
hasher.combine(name)
}
}
final class JSONStore: DataStore {
typealias Configuration = JSONStoreConfiguration
typealias Snapshot = DefaultSnapshot
var configuration: JSONStoreConfiguration
var name: String
var schema: Schema
var identifier: String
init(_ configuration: JSONStoreConfiguration, migrationPlan: (any SchemaMigrationPlan.Type)?) throws {
self.configuration = configuration
self.name = configuration.name
self.schema = configuration.schema!
self.identifier = configuration.fileURL.lastPathComponent
}
func save(_ request: DataStoreSaveChangesRequest<DefaultSnapshot>) throws -> DataStoreSaveChangesResult<DefaultSnapshot> {
var remappedIdentifiers = [PersistentIdentifier: PersistentIdentifier]()
var serializedData = try read()
for snapshot in request.inserted {
let permanentIdentifier = try PersistentIdentifier.identifier(for: identifier,
entityName: snapshot.persistentIdentifier.entityName,
primaryKey: UUID())
let permanentSnapshot = snapshot.copy(persistentIdentifier: permanentIdentifier)
serializedData[permanentIdentifier] = permanentSnapshot
remappedIdentifiers[snapshot.persistentIdentifier] = permanentIdentifier
}
for snapshot in request.updated {
serializedData[snapshot.persistentIdentifier] = snapshot
}
for snapshot in request.deleted {
serializedData[snapshot.persistentIdentifier] = nil
}
try write(serializedData)
return DataStoreSaveChangesResult<DefaultSnapshot>(for: self.identifier, remappedIdentifiers: remappedIdentifiers)
}
func fetch<T>(_ request: DataStoreFetchRequest<T>) throws -> DataStoreFetchResult<T, DefaultSnapshot> where T : PersistentModel {
if request.descriptor.predicate != nil {
throw DataStoreError.preferInMemoryFilter
} else if request.descriptor.sortBy.count > 0 {
throw DataStoreError.preferInMemorySort
}
let objs = try read()
let snapshots = objs.values.map({ $0 })
return DataStoreFetchResult(descriptor: request.descriptor, fetchedSnapshots: snapshots, relatedSnapshots: objs)
}
func read() throws -> [PersistentIdentifier : DefaultSnapshot] {
if FileManager.default.fileExists(atPath: configuration.fileURL.path(percentEncoded: false)) {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let data = try decoder.decode([DefaultSnapshot].self, from: try Data(contentsOf: configuration.fileURL))
var result = [PersistentIdentifier: DefaultSnapshot]()
data.forEach { s in
result[s.persistentIdentifier] = s
}
return result
} else {
return [:]
}
}
func write(_ data: [PersistentIdentifier : DefaultSnapshot]) throws {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
let jsonData = try encoder.encode(data.values.map({ $0 }))
try jsonData.write(to: configuration.fileURL)
}
}
The data model classes:
import SwiftData
@Model
class Settings {
private(set) var version = 1
@Relationship(deleteRule: .cascade) var hack: Hack? = Hack()
init() {
}
}
@Model
class Hack {
var foo = "Foo"
var bar = 42
init() {
}
}
Container:
lazy var mainContainer: ModelContainer = {
do {
let url = // URL to file
let configuration = JSONStoreConfiguration(name: "Settings", schema: Schema([Settings.self, Hack.self]), fileURL: url)
return try ModelContainer(for: Settings.self, Hack.self, configurations: configuration)
}
catch {
fatalError("Container error: \(error.localizedDescription)")
}
}()
Load function, that saves a new Settings JSON file if there isn't an existing one:
@MainActor func loadSettings() {
let mainContext = mainContainer.mainContext
let descriptor = FetchDescriptor<Settings>()
let settingsArray = try? mainContext.fetch(descriptor)
print("\(settingsArray?.count ?? 0) settings found")
if let settingsArray, let settings = settingsArray.last {
print("Loaded")
} else {
let settings = Settings()
mainContext.insert(settings)
do {
try mainContext.save()
} catch {
print("Error saving settings: \(error)")
}
}
}
The save operation creates a JSON file, which while it isn't a format I would choose, is acceptable, though I notice that the "hack" property (the relationship) doesn't have the correct identifier.
When I run the app again to load the data, I get an error (that there wasn't room to include in this post).
Even if I change Apple's code to not assign a new identifier, so the relationship property and its pointee have the same identifier, it still doesn't load.
Am I doing something obviously wrong, or are relationships not supported in custom data stores?
I work on an app that saves data to the Documents folder in the users iCloud Drive. This uses the iCloud -> iCloud Documents capability with a standard container.
We've noticed an issue where a user will delete the apps data by doing to Settings > {Name} > iCloud > Storage > App Name > select "delete data from iCloud", and then our app can no longer write to or create the Documents folder.
Once that happens, we get this error:
Error Domain=NSCocoaErrorDomain Code=513 "You don't have permission to save the file "Documents" in the folder "iCloud~your~bundle~identifier"." UserInfo={NSFilePath=/private/var/mobile/Library/Mobile Documents/iCloud~your~bundle~identifier/Documents, NSURL=file:///private/var/mobile/Library/Mobile%20Documents/iCloud~your~bundle~identifier/Documents, NSUnderlyingError=0x1102c7ea0 {Error Domain=NSPOSIXErrorDomain Code=13 "Permission denied"}}
This is reproducible using the sample project here https://developer.apple.com/documentation/uikit/synchronizing-documents-in-the-icloud-environment.
Steps to reproduce in that project:
Tap the plus sign in the top right corner to create a new document
Add a document name and tap "Save to Documents"
Go to Settings > {Name} > iCloud > Storage > SimpleiCloudDocument App Name > select "delete data from iCloud"
Reopen the app and repeat steps 1-2
Observe error on MainViewController+Document.swift:59
Deleting and reinstalling the app doesn't seem to help.
Topic:
App & System Services
SubTopic:
iCloud & Data
Hello everyone,
I am experiencing a persistent authentication error when querying a custom user profile record, and the error message seems to be a red herring.
My Setup:
I have a custom CKRecord type called ColaboradorProfile.
When a new user signs up, I create this record and store their hashed password, salt, nickname, and a custom field called loginIdentifier (which is just their lowercase username).
In the CloudKit Dashboard, I have manually added an index for loginIdentifier and set it to Queryable and Searchable. I have deployed this schema to Production.
The Problem:
During login, I run an async function to find the user's profile using this indexed loginIdentifier.
Here is the relevant authentication code:
func autenticar() async {
// ... setup code (isLoading, etc.)
let lowercasedUsername = username.lowercased()
// My predicate ONLY filters on 'loginIdentifier'
let predicate = NSPredicate(format: "loginIdentifier == %@", lowercasedUsername)
let query = CKQuery(recordType: "ColaboradorProfile", predicate: predicate)
// I only need these specific keys
let desiredKeys = ["password", "passwordSalt", "nickname", "isAdmin", "isSubAdmin", "username"]
let database = CKContainer.default().publicCloudDatabase
do {
// This is the line that throws the error
let result = try await database.records(matching: query, desiredKeys: desiredKeys, resultsLimit: 1)
// ... (rest of the password verification logic)
} catch {
// The error always lands here
logDebug("Error authenticating with CloudKit: \(error.localizedDescription)")
await MainActor.run {
self.errorMessage = "Connection Error: \(error.localizedDescription)"
self.isLoading = false
self.showAlert = true
}
}
}
The Error:
Even though my query predicate only references loginIdentifier, the catch block consistently reports this error:
Error authenticating with CloudKit: Field 'createdBy' is not marked queryable.
I know createdBy (the system creatorUserRecordID) is not queryable by default, but my query isn't touching that field. I already tried indexing createdBy just in case, but the error persists. It seems CloudKit cannot find or use my index for loginIdentifier and is incorrectly reporting a fallback error related to a system field.
Has anyone seen this behavior? Why would CloudKit report an error about createdBy when the query is explicitly on an indexed, custom field?
I'm new to Swift and I'm struggling quite a bit.
Thank you,