Hello, thank you Apple for supporting custom store with SwiftData and the Schema type is superb to work with. I have successfully set one up with SQL and have some feedback and issues regarding its APIs.
There’s a highlighted message in the documentation about not using internal restricted symbols directly, but they contradict with the given protocols and I am concerned about breaking any App Store rules. Are we allowed to use these? If not, they should be opened up as they’re useful.
BackingData is required to set up custom snapshots, initialization, and getting/setting values. And I want to use it with createBackingData() to directly initialize instances from snapshots when transferring them between server and client or concurrency.
RelationshipCollection for casting to-many relationships from backing data or checking if an array contains a PersistentModel.
SchemaProperty for type erasure in a collection.
Schema.Relationship has KeyPath properties, but it is missing for Schema.Attribute and Schema.CompositeAttribute. Which means you can’t purely depend on the schema to map data. I am unable to access the properties of a custom struct type in a predicate unless I use Mirror with schemaMetadata() or CustomStringConvertible on the KeyPath directly to extract it.
Trivial, but… the KeyPath property name is inconsistent (it’s all lowercase).
It would be nice to retrieve property names from custom struct types, since you are unable access CodingKeys that are auto synthesized by Codable for structs. But I recently realized they’re a part Schema.CompositeAttribute, however I don’t know how to match these without the KeyPath…
I currently map my entities using CodingKeys to their PredicateCodableKeyPathProviding.… but I wish for a simpler alternative!
It’s unclear how to provide the schema to the snapshot before new models are created.
I currently use a static property, but I want to make it flexible if more schemas and configurations are added later on.
I considered saving and loading the schema in a temporary location, but doubtful that the KeyPath values will be available as they are not Codable.
I suspect schemaMetadata() has the information I need to map the backing data without a schema for snapshots, but as mentioned previously, properties are inaccessible…
Allow access to entity metatypes, like value types from SchemaProperty. They’re useful for getting data out of snapshots and casting them to CodingKeys and PredicateCodableKeyPathProviding. They do not carry over when you provide them in the Schema.
I am unable to retrieve the primary key from PersistentIdentifier.
It seems like once you create one, you can’t get it out, like the DataStoreConfiguration in ModelContainer is not the one you used to set it up. I cannot cast it, it is an entirely different struct?
I have to use JSONSerialization to extract it, but I want to get it directly since it is not a column in my database. It is transformed when it goes to/from my tables.
It’s unknown how to support some schema options, such as Spotlight and CloudKit.
Allow for extending macro options, such as adding options to set as primary key, whether to auto increment, etc…
You can create a schema for super and sub entities, but it doesn’t appear you can actually set them up from the @Model macro or use inheritance on these models…
SwiftData history tracking seems incomplete for HistoryDelete, because that protocol requires HistoryTombstone, but this type cannot be instantiated, nor does it contain anything useful to infer from.
As an aside, I want to create my own custom ModelActor that is a global actor. However, I’m unable to replicate the executor that Apple provides where the executor has a ModelContext, because this type does not conform to Sendable. So how did Apple do this? The documentation doesn’t mention unchecked Sendable, but I figure if the protocol is available then we would be able to set up our own.
And please add concurrency features!
Anyway, I hope for more continued support in the future and I am looking forward to what’s new this WWDC! 😊
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
CloudKit CKRecordZone Deletion Issue
Problem: CloudKit record zones deleted via CKDatabase.modifyRecordZones(deleting:) or CKModifyRecordZonesOperation are successfully
removed but then reappear. I suspect they are automatically reinstated by CloudKit sync, despite successful deletion confirmation.
Environment:
SwiftData with CloudKit integration
Custom CloudKit zones created for legacy zone-based sharing
Observed Behavior:
Create custom zone (e.g., "TestZone1") via CKDatabase.modifyRecordZones(saving:)
Copy records to zone for sharing purposes
Delete zone using any CloudKit deletion API - returns success, no errors
Immediate verification: Zone is gone from database.allRecordZones()
After SwiftData/CloudKit sync or app restart: Zone reappears
Reproduction:
Tested with three different deletion methods - all exhibit same behaviour:
modifyRecordZones(deleting:) async API
CKModifyRecordZonesOperation (fire-and-forget)
CKModifyRecordZonesOperation with result callbacks
Zone deletion succeeds, change tokens (used to track updates to shared records) cleaned up
But zones are restored presumably by CloudKit background sync
Expected: Deleted zones should remain deleted
Actual: Zones are reinstated, creating orphaned zones
Hi all,
I am using SwiftData and cloudkit and I am having an extremely persistent bug.
I am building an education section on a app that's populated with lessons via a local JSON file. I don't need this lesson data to sync to cloudkit as the lessons are static, just need them imported into swiftdata so I've tried to use the modelcontainer like this:
static func createSharedModelContainer() -> ModelContainer {
// --- Define Model Groups ---
let localOnlyModels: [any PersistentModel.Type] = [
Lesson.self, MiniLesson.self,
Quiz.self, Question.self
]
let cloudKitSyncModels: [any PersistentModel.Type] = [
User.self, DailyTip.self, UserSubscription.self,
UserEducationProgress.self // User progress syncs
]
However, what happens is that I still get Lesson and MiniLesson record types on cloudkit and for some reason as well, whenever I update the data models or delete and reinstall the app on simulator, the lessons duplicate (what seems to happen is that a set of lessons comes from the JSON file as it should), and then 1-2 seconds later, an older set of lessons gets synced from cloudkit.
I can delete the old set of lessons if I just delete the lessons and mini lessons record types, but if I update the data model again, this error reccurrs.
Sorry, I don't know if I managed to explain this well but essentially I just want to stop the lessons and minilessons from being uploaded to cloudkit as I think this will fix the problem. Am I doing something wrong with the code?
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'm beta-testing a CloudKit-based app. One of my testers suddenly reported that they got a .badContainer CloudKit error:
<CKError 0x302619800:"Bad Container" (5/1014); server message = "Invalid container to get bundle ids"; op = <...>; uuid = <...>; container ID = "<...>">
(all private info replaced with <...>)
The container ID in the message was exactly what I expected, and exactly what other users are successfully using.
When I followed up on the report, the user said she tried again later and everything was fine. It's still working fine days later.
What could cause a user to get a .badContainer message, when all other users using the same app are fine, the container ID makes sense, and future runs work fine?
Is this something I need to worry about? Does it maybe sometimes happen when CloudKit is having some kind of outage?
Description:
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.
Problem Details:
I'm migrating from schema version V2 to V3, which involves:
Renaming Person class to GroupData
Keeping the same data structure but changing the class name
Using a custom migration stage to copy data from old to new schema
Migration Code:
swift
static let migrationV2toV3 = MigrationStage.custom(
fromVersion: LinkMapV2.self,
toVersion: LinkMapV3.self,
willMigrate: { context in
do {
let persons = try context.fetch(FetchDescriptor<LinkMapV2.Person>())
print("Found (persons.count) Person objects to migrate") // ✅ Shows 11 objects
for person in persons {
let newGroup = LinkMapV3.GroupData(
id: person.id, // Same UUID
name: person.name,
// ... other properties
)
context.insert(newGroup)
print("Inserted GroupData: '\(newGroup.name)'") // ✅ Confirms insertion
}
try context.save() // ✅ No error thrown
print("Successfully migrated \(persons.count) objects") // ✅ Confirms save
} catch {
print("Migration error: \(error)")
}
},
didMigrate: { context in
do {
let groups = try context.fetch(FetchDescriptor<LinkMapV3.GroupData>())
print("Final GroupData count: \(groups.count)") // ❌ Shows 0 objects!
} catch {
print("Verification error: \(error)")
}
}
)
Console Output:
text
=== MIGRATION STARTED ===
Found 11 Person objects to migrate
Migrating Person: 'Riverside of pipewall' with ID: 7A08C633-4467-4F52-AF0B-579545BA88D0
Inserted new GroupData: 'Riverside of pipewall'
... (all 11 objects processed) ...
=== MIGRATION COMPLETED ===
Successfully migrated 11 Person objects to GroupData
=== MIGRATION VERIFICATION ===
New GroupData count: 0 // ❌ PROBLEM: No objects found!
What I've Tried:
Multiple context approaches:
Using the provided migration context
Creating a new background context with ModelContext(context.container)
Using context.performAndWait for thread safety
Different save strategies:
Calling try context.save() after insertions
Letting SwiftData handle saving automatically
Multiple save calls at different points
Verification methods:
Checking in didMigrate closure
Checking in app's ContentView after migration completes
Using both @Query and manual FetchDescriptor
Schema variations:
Direct V2→V3 migration
Intermediate V2.5 schema with both classes
Lightweight migration with @Attribute(originalName:)
Current Behavior:
Migration runs without errors
Objects appear to be inserted successfully
context.save() completes without throwing errors
But queries in didMigrate and post-migration return empty results
The objects seem to exist in a temporary state that doesn't persist
Expected Behavior:
Objects created during migration should be persisted and queryable
Post-migration queries should return the migrated objects
Data should be available in the main app after migration completes
Environment:
Xcode 16.0+
iOS 18.0+
SwiftData
Swift 6.0+
Key Questions:
Is there a specific way migration contexts should be handled for data to persist?
Are there known issues with object persistence in custom migrations?
Should we be using a different approach for class renaming migrations?
Is there a way to verify that objects are actually being written to the persistent store?
The migration appears to work perfectly until the verification step, where all created objects seem to vanish. Any guidance would be greatly appreciated!
Additional Context from my investigation:
I've noticed these warning messages during migration that might be relevant:
text
SwiftData.ModelContext: Unbinding from the main queue. This context was instantiated on the main queue but is being used off it.
error: Persistent History (76) has to be truncated due to the following entities being removed: (Person)
This suggests there might be threading or context lifecycle issues affecting persistence.
Let me know if you need any additional information about my setup or migration configuration!
Something has caused my CloudKit queries to fail. On the dashboard I get an error message "Failed to execute query" when I try to "SORT BY" a field. The field is listed under Indexes as "sortable". For a different field, when I enter the field under "FILTER BY", and before I tap "Query", I get "No results". That field is listed under the Indexes as "queryable".
It used to work fine.
I have described this further, with screenshots at FB16114560
I am trying to implement record sharing in my project, but when I try to copy the link on the UICloudSharingController, the sheet closes and the link doesn't get copied.
My CloudKitManager function:
public func shareTeam(_ team: Team) -> AnyPublisher<CKShare, Error> {
Future { [weak self] promise in
guard let self = self else {
promise(.failure(CloudKitError.unknown))
return
}
let record = team.toCKRecord()
let share = CKShare(rootRecord: record)
share[CKShare.SystemFieldKey.title] = "Join \(team.name)" as CKRecordValue
share.publicPermission = .readWrite
let operation = CKModifyRecordsOperation(recordsToSave: [record, share], recordIDsToDelete: nil)
operation.savePolicy = .ifServerRecordUnchanged
operation.qualityOfService = .userInitiated
operation.modifyRecordsResultBlock = { result in
switch result {
case .success:
promise(.success(share))
case .failure(let error):
promise(.failure(error))
}
}
self.privateDatabase.add(operation)
}
.eraseToAnyPublisher()
}
ViewModel function:
func shareTeam() {
guard let selectedTeam = selectedTeam else { return }
CloudKitManager.shared.shareTeam(selectedTeam)
.receive(on: DispatchQueue.main)
.sink { [weak self] completion in
switch completion {
case .finished:
break
case .failure(let error):
self?.didError = true
self?.error = error
}
} receiveValue: { share in
let sharePresenter = SharePresenter(
share: share,
container: CloudKitManager.shared.container,
teamName: selectedTeam.name,
rootRecord: selectedTeam.toCKRecord()
)
sharePresenter.presentShareSheet()
}
.store(in: &cancellables)
}
Здравствуйте, я зашел на чужой Apple ID под предлогом мошенников и теперь не могу выйти, помогите мне пожалуйста, это полностью мой телефон и я смогу доказать, что он мой.
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!
Hello the documentation for message filtering has been offline for a few days now, is it possible to get it back, or is there somewhere else it can be viewed in the meanwhile?
https://developer.apple.com/documentation/sms_and_call_reporting/sms_and_mms_message_filtering
(I just chose topic/tags at random, there aren't any relevant for this)
My app uses iCloud to let users sync their files via their private iCloud Drive, which does not use CloudKit.
FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appending(component: "Documents")
I plan to transfer my app to another developer account, but I'm afraid it will affect the access of the app to the existing files in that folder. Apple documentation doesn't mention this case.
Has anyone done this before and can confirm if the app will continue to work normally after transferring?
Thanks
is there a way to fix this?
Topic:
App & System Services
SubTopic:
iCloud & Data
I'm developing an app that uses CloudKit synchronization with SwiftData and on visionOS I added an App Settings bundle. I have noticed that sometimes, when the app is open and the user changes a setting from the App Settings bundle, the following fatal error occurs:
SwiftData/BackingData.swift:831: Fatal error: This model instance was destroyed by calling ModelContext.reset and is no longer usable.
The setting is read within the App struct in the visionOS app target using @AppStorage and this value is in turn used to set the passthrough video dimming via the .preferredSurroundingsEffect modifier. The setting allows the user to specify the dimming level as dark, semi dark, or ultra dark.
The fatal error appears to occur intermittently although the first time it was observed was after adding the settings bundle. As such, I suspect there is some connection between those code changes and this fatal error even though they do not directly relate to SwiftData.
Document based SwiftData apps do not autosave changes to the ModelContext at all. This issue has been around since the first release of this SwiftData feature.
In fact, the Apple WWDC sample project (https://developer.apple.com/documentation/swiftui/building-a-document-based-app-using-swiftdata) does not persist any data in its current state, unless one inserts modelContext.save() calls after every data change.
I have reported this under the feedback ID FB16503154, as it seemed to me that there is no feedback report about the fundamental issue yet.
Other posts related to this problem:
https://forums.developer.apple.com/forums/thread/757172
https://forums.developer.apple.com/forums/thread/768906
https://developer.apple.com/forums/thread/764189
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
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?
I haven't been able to find a definitive answer to this online. Is NSUbiquitousKeyValueStore end-to-end encrypted with Advanced Data Protection turned on? It’s not specifically called out here https://support.apple.com/en-us/102651.
Topic:
App & System Services
SubTopic:
iCloud & Data
Hey,
For some reason I see crashes for my iOS app related to CloudKit entitlements.
The crash happens on start up and it says:
"CKException - Application has malformed entitlements. Found value "*" for entitlement com.apple.developer.icloud-services, expected an array of strings"
I have checked my entitlements of the same build on App Store Connect and it shows "com.apple.developer.icloud-services: ( "CloudKit" )"
So I am not sure why users are having this issue. I haven't been able to reproduce it.
Does anyone have any idea why this is happening?
Thanks
Does anyone have this error and my app can't be searched in the Apple Store