I'm using SwiftData with CloudKit and have been trying to migrate from
SchemaV1 to SchemaV2, but it seems reducing the Entities crashes my app.
// Example of migrating from V1 to V2
// Dropping `Person` because it's no longer needed
do {
// SchemaV1: Person.self, Author.self
// SchemaV2: Author.self
let schema = Schema(versionedSchema: SchemaV2.self)
return try ModelContainer(
for: schema,
migrationPlan: AppSchemaMigrationPlan.self,
configurations: ModelConfiguration(
cloudKitDatabase: .automatic)
)
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
Is it possible to drop Entities in the Schema Migration Plan?
How can I delete the Person model from my Schema and CloudKit?
CloudKit
RSS for tagStore structured app and user data in iCloud containers that can be shared by all users of your app using CloudKit.
Posts under CloudKit tag
200 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
I am attempting to migrate a cloudkit module that calls on manual cloudkit methods for fetching record zone changes, modifying records, etc to one that utilizes CKSyncEngine. I've got a basic implementation working with just a create method for one of my data models, however it seems like the sync engine keeps calling sync events on the same pending changes.
Here is my current flow:
The user will hit some button that lets them fill out a form to create a data model.
The user saves the form. This triggers a method that takes the resulting data model and queues it to the sync engine's state (engine.state.add(pendingRecordZoneChanges: pendingChanges)
I have my delegate method nextRecordZoneChangeBatch(_ context:...) implemented where it fetches the corresponding data model using the record ID and returns a batch containing the corresponding populated record from the data model.
I have the handleEvent(_ event:...) delegate method implemented where I handle both .fetchRecordZoneChanges and .sentRecordZoneChanges. I have set up .sentRecordZoneChanges to merge the server record into my local record (and persisted locally) so that the record change tags are the same.
After this last portion, it seems that the sync engine continues to keep pushing syncs/updates and I end up with numerous handleEvent(_ event:) calls that keep returning savedRecords (and occasionally failedRecordSaves).
Am I missing some step to remove the record from the changes after the sync engine recognizes that I have properly saved the record to the server?
In a CloudKit private database, the Owner creates a custom zone and performs the following actions:
Creates CKRecord1 with CKShare1 and invites Participant1 to it.
Creates CKRecord2 with CKShare2 and invites Participant2 to it.
Creates CKRecordShared, which should be accessible to both Participant1 and Participant2.
How can I achieve step 3?
I observed that:
Setting a regular reference from CKRecord1 (or CKRecord2) to CKRecordShared does not automatically make CKRecordShared accessible to Participant1 (or Participant2).
CKRecordShared can only have one parent, so it cannot be directly linked via parent reference to both Participant1 and Participant2 at the same time.
One potential solution I see is to have the Owner create a separate CKShare for CKRecordShared and share it explicitly with each participant. However, this approach could lead to user errors, as it requires careful management of multiple shares for each participant.
Is there a better way to handle this scenario, ensuring that CKRecordShared is accessible to multiple participants without introducing unnecessary complexity or potential errors?
Hi,
I've been using Core Data + CloudKit via NSPersistentCloudKitContainer for several years now. Back then I just created my Core Data AND CloudKit fields by hand.
Now the time has come for a little lightweight migration to a new Core Data model, let's say I just needed to add one String attribute.
So I've done the Core Data local migration as usual, then added this to container code:
try? persistentContainer.initializeCloudKitSchema(options: NSPersistentCloudKitContainerSchemaInitializationOptions())
Run. And everything worked great. but…
Now I've noticed that CloudKit created new CKAsset fields for each String attribute that I had in Core Data (about 5 new CKAsset fields). Is this normal!? Why?
! Is it safe to deploy these changes to prod?
ty.
ChatGPT said: "This field is used internally by CloudKit to handle large string values. If the string value is small enough, it is stored in the normal String field, but if it exceeds the size limit (about 1KB), the string is automatically stored as a CKAsset."
My question
Is there a way to perform an iCloud keychain reset in order to be able to test CKErrorUserDidResetEncryptedDataKey ?
I found this section in the CloudKit documentation
https://developer.apple.com/documentation/cloudkit/encrypting-user-data#Handle-a-User-Keychain-Reset
I want to be prepared for the zoneNotFound / CKErrorUserDidResetEncryptedDataKey case.
However, I can't find a way to actually reproduce this error with an iCloud (test-) user and can't find any Apple documentation on how to perform sucha "User Keychain Reset".
The only thing that almost looked like it I came across was in the Keychain.app's Settings "Reset Default Keychains…". However, performing this didn't seem to affect the CloudKit data used in our App at all.
I've been trying to do this with an Apple account that has 2FA active and a recovery account assigned.
We're only targetting >= iOS 18, macOS >= 15.
I followed these two resources and setup history tracking in SwiftData.
SwiftData History tracking:
Track model changes with SwiftData history
For data deduplication: Sharing Core Data objects between iCloud users
In the sample app (CoreDataCloudKitShare), a uuid: UUID field is used for deduplication logic. It sorts the entities by their uuids, reserves the first one, and marks the others as deduplicated.
Would it be a viable solution to use a createdAt: Date field for the sort operation instead of a uuid field, or are dates inherently problematic?
I have been trying to get this to work since it was announced a few years ago but with no joy. I'm struggling to get Apple's example code to behave itself too. Seems overly complex and buggy. So I set out to create a simplified version myself. I have got the database to sync with CloudKit and I can see my records in the developer dashboard. I'm trying to use container.record(for: object.objectID) to get the CKRecord for it, but this always fails. The next step would be to add the participant.
I try to add the participant based on this code:
Button
{
let record = fetchRecord(for: items[0]) //hack just to use the first record for dev testing
let share = CKShare(rootRecord: record)
let persistenceController = PersistenceController.shared
persistenceController.addParticipant(
emailAddress: "andrew@ambrit.com",
permission: .readWrite,
share: share)
{ share, error in
if let error = error
{
print("Error: \(error.localizedDescription)")
}
else if let share = share
{
print("Share updated successfully: \(share)")
}
}
}
label:
{
Label("Participants", systemImage: "person")
}
and
extension PersistenceController
{
func addParticipant(emailAddress: String, permission: CKShare.ParticipantPermission = .readWrite, share: CKShare,
completionHandler: ((_ share: CKShare?, _ error: Error?) -> Void)?)
{
let container = PersistenceController.shared.container
let lookupInfo = CKUserIdentity.LookupInfo(emailAddress: emailAddress)
let persistentStore = privatePersistentStore //share.persistentStore!
container.fetchParticipants(matching: [lookupInfo], into: persistentStore) { (results, error) in
guard let participants = results, let participant = participants.first, error == nil else
{
completionHandler?(share, error)
return
}
participant.permission = permission
participant.role = .privateUser
share.addParticipant(participant)
container.persistUpdatedShare(share, in: persistentStore)
{ (share, error) in
if let error = error
{
print("\(#function): Failed to persist updated share: \(error)")
}
completionHandler?(share, error)
}
}
}
}
My immediate problem is that when I call fetchRecord it doesn't find anything despite the record being available in the CloudKit dashboard.
func fetchRecord(for object: NSManagedObject) -> CKRecord
{
let container = PersistenceController.shared.container
print ("Fetching record \(object.objectID)")
if let record = container.record(for: object.objectID)
{
print("CKRecord ID: \(record.recordID)")
print("Record Name: \(record.recordID.recordName)")
return record
}
else
{
fatalError("Record not found")
}
}
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
In Apple Numbers and similar apps, a user can save a document to iCloud Drive, and collaborate with other users. From what I can gather, it seems to use two mechanisms: the document as a whole is synced via iCloud Drive, but when a collaboration is started, it seems to use CloudKit records to do live updates.
I am working on a similar app, that saves documents to iCloud Drive (on Mac, iPad, and iPhone). Currently it only syncs via iCloud Drive, re-reading the entire (often large) document when a remote change occurs. This can lead to a delay of several seconds (up to a minute) for the document to be saved, synced to the server, synced from the server, and re-read.
I'm working on adding a "live sync", i.e. the ability to see changes in as near to real-time as feasible, like in Apple's apps.
The document as a whole will remain syncing via iCloud Drive. My thought is to add a CloudKit CKRecord-based sync when two or more users are collaborating on a document, recording only the diffs for quick updates. The app would no longer re-read the entire document when iCloud Drive updates it while in use, and would instead read the CloudKit records and apply those changes. This should be much faster.
Is my understanding of how Apple does it correct? Does my proposed approach seem sensible? Has anyone else implemented something like this, with iCloud Drive-based documents and a CloudKit live sync?
In terms of technologies, I see that Apple now has a Shared with You framework, with the ability to use a NSItemProvider to start the collaboration. Which raises the question, should I use the iCloud Drive document for the collaboration (as I do now), or the CloudKit CKShare diff? I think I'd have to use the document as a whole, both so it works with the Send Copy option, and so a user that doesn't have the document gets it when using Collaborate. Once the collaboration is underway, I'd want to start the CloudKit channel. So I guess I'd save the CKShare to the server, get its URL, and save that in the document, so another user can read that URL as part of their initial load of the document from iCloud Drive?
Once two (or more) users have the document via iCloud Drive, and the CKShare via the embedded URL, I should be able to do further live-sync updates via CloudKit. If a user closes the document and re-opens it, they'd get the updates via iCloud Drive, so no need to apply any updates from before the document was opened.
Does all this sound reasonable, or am I overlooking some gotcha? I'd appreciate any advice from people who have experience with this kind of syncing.
I have encountered an issue with a customer’s data access after they migrated to a different iCloud account, and I’m looking for guidance.
The Situation:
The customer was logged into their account on my app, which was associated with a specific iCloud account (iCloud A).
They had all their app data available while using iCloud A.
The customer then switched to a new iCloud account (iCloud B) on the same device, while still using the same app account.
After switching iCloud accounts, their data is no longer visible in the app or my CloudKit dashboard.
My Investigation:
I accessed the customer’s CloudKit data via the CloudKit Console, acting as their iCloud account.
I couldn’t find the private database zone or any of their records when accessing iCloud A through the console.
I don’t believe the data was deleted since actions performed under iCloud B shouldn’t affect data stored in iCloud A.
My Hypothesis:
I suspect that the customer’s old iCloud account (iCloud A) may have downgraded or stopped paying for iCloud storage.
If the iCloud subscription is inactive or expired, could that prevent me from accessing their CloudKit data?
Would renewing the iCloud subscription for iCloud A restore access to the missing data?
Questions:
Does an unpaid or expired iCloud account restrict access to CloudKit records, even if they weren’t deleted?
Would paying for iCloud storage again restore the data previously stored in CloudKit?
Is there any way to recover the customer’s CloudKit data if they are unable to access their old iCloud account?
If anyone has a simpler approach to recovering the customer’s iCloud-stored app data or has experience dealing with iCloud migrations like this, I’d appreciate your insights. Thank you in advance for any advice!
Hi there
We're using CloudKit in our app which, generally, syncs data perfectly between devices. However, recently the sync has stopped working (some changes will never sync and the sync is delayed for several days even with the app open on all devices). CloudKit's logs show the error „You can't save and delete the same record" and „Already have a mirrored relationship registered for this key", etc. We’ve a hunch that this issue is related to a mirrored relationship of one database entity.
Our scenario:
We've subclassed the database entities.
The database model (which we can't share publicly) contains mirrored relationships.
We store very long texts in the database (similar to a Word document that contains markup data – in case that’s relevant).
Deleting all data and starting with a completely new container and bundle identifier didn’t help (we tried that multiple times).
This issue occurs on macOS (15.2(24C101) as well on iOS (18.2).
Any hints on how to get the sync working again? Should we simply avoid mirrored relationships?
Many thanks
I am unable to download file from iCloud Documents from my swift project. I was able to successfully upload the file to iCloud Documents but now when I try to download same file at the uploaded path. It is giving me error that I don't have permission to view the file.
Please find exact error below.
[ERROR] couldn't fetch remote operation IDs: NSError: Cocoa 257 "The file couldn’t be opened because you don’t have permission to view it."
"Error returned from daemon: Error Domain=com.apple.accounts Code=7 "(null)""
Hello,
When attempting to create a CKShare on a personal device linked to a Family iCloud plan (non-primary account holder), the operation fails with a quotaExceeded error. This occurs with the Family plan having 1.5TB available storage space.
This is also causing a data loss for the object(s) that were attempted to be shared.
Details
Account Type: Family iCloud Plan (2TB total storage)
Current Family Usage: 399GB
iCloud Account Usage: 70 GB
Steps to Reproduce:
Have an iCloud account with storage over the 5GB free space limit.
Be on a part of a iCloud Family Plan as the non-primary account holder.
Have storage space available in the Family Plan
Attempt to start a CloudKit Share/Collaboration on the device.
Observe that the CKShare creation fails with a quotaExceeded error.
Expected Behavior:
The CKShare should be successfully created, reflecting the total available storage of the Family plan.
Observed Behavior:
The CKShare fails to be created with quotaExceeded.
Additional Testing
On a test device using an iCloud account with no stored data, the CKShare was created successfully and shared without issue.
Suspected Cause
The CKShare functionality is verifying the personal storage allocation of the iCloud account and failing without checking total available storage provided by the Family plan.
I am having problems when I first loads the app. The time it takes for the Items to be sync from my CloudKit to my local CoreData is too long.
Code
I have the model below defined by my CoreData.
public extension Item {
@nonobjc class func fetchRequest() -> NSFetchRequest<Item> {
NSFetchRequest<Item>(entityName: "Item")
}
@NSManaged var createdAt: Date?
@NSManaged var id: UUID?
@NSManaged var image: Data?
@NSManaged var usdz: Data?
@NSManaged var characteristics: NSSet?
@NSManaged var parent: SomeParent?
}
image and usdz columns are both marked as BinaryData and Attribute Allows External Storage is also selected.
I made a Few tests loading the data when the app is downloaded for the first time. I am loading on my view using the below code:
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.createdAt, ascending: true)]
)
private var items: FetchedResults<Item>
var body: some View {
VStack {
ScrollView(.vertical, showsIndicators: false) {
LazyVGrid(columns: columns, spacing: 40) {
ForEach(items, id: \.self) { item in
Text(item.id)
}
}
}
}
}
Test 1 - Just loads everything
When I have on my cloudKit images and usdz a total of 100mb data, it takes around 140 seconds to show some data on my view (Not all items were sync, that takes much longer time)
Test 2 - Trying getting only 10 items at the time ()
This takes the same amount of times the long one . I have added the following in my class, and removed the @FetchRequest:
@State private var items: [Item] = [] // CK
@State private var isLoading = false
@MainActor
func loadMoreData() {
guard !isLoading else { return }
isLoading = true
let fetchRequest = NSFetchRequest<Item>(entityName: "Item")
fetchRequest.predicate = NSPredicate(format: "title != nil AND title != ''")
fetchRequest.fetchLimit = 10
fetchRequest.fetchOffset = items.count
fetchRequest.predicate = getPredicate()
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Item.createdAt, ascending: true)]
do {
let newItems = try viewContext.fetch(fetchRequest)
DispatchQueue.main.async {
items.append(contentsOf: newItems)
isLoading = false
}
} catch {}
}
Test 2 - Remove all images and usdz from CloudKit set all as Null
Setting all items BinaryData to null, it takes around 8 seconds to Show the list.
So as we can see here, all the solutions that I found are bad. I just wanna go to my CloudKit and fetch the data with my CoreData. And if possible to NOT fetch all the data because that would be not possible (imagine the future with 10 or 20GB or data) What is the solution for this loading problem? What do I need to do/fix in order to load lets say 10 items first, then later on the other items and let the user have a seamlessly experience?
Questions
What are the solutions I have when the user first loads the app?
How to force CoreData to query directly cloudKit?
Does CoreData + CloudKit + NSPersistentCloudKitContainer will download the whole CloudKit database in my local, is that good????
Storing images as BinaryData with Allow external Storage does not seems to be working well, because it is downloading the image even without the need for the image right now, how should I store the Binary data or Images in this case?
I just released an App update the didn't touch ANYTHING to do with Core Data (nothing changed in our Coredata code for at least 8 months). The update uses SDK for iOS 18 and Xcode 16.2 and the app now requires iOS 18 and was a minor bug patch and UI improvements for recent iOS changes.
Since the update we are getting a steady trickle of users on iOS 18, some who allow the App to store data in iCloud (Cloudkit) and others who do not, all reporting that after the update to our recent release ALL their data is gone?!
I had not seen this on ANY device until today when I asked a friend who uses the App if they had the issue and it turned out they did, so I hooked their device up to Xcode and ALL the data in the CoreData database was gone?! They are NOT using iCloud. There were no errors or exceptions on Xcode console but a below code returned NO records at all?!
Chart is custom entity and is defined as:
@interface Chart : NSManagedObject {}
let moc = pc.viewContext
let chartsFetch = NSFetchRequest<NSFetchRequestResult>(entityName:"Charts") // Fetch all Charts
do {
let fetchedCharts = try moc.fetch(chartsFetch) as! [Chart]
for chart in fetchedCharts {
....
}
}
A break point inside the do on fetchedCharts show there are NO objects returned.
This is a serious issue and seems like an iOS 18 thing. I saw some people talking in here about NSFetchRequest issues with iOS 18. I need some guidance here from someone Apple engineer here who knows what the status of these NSFetchrequest bugs are and what possible workarounds are. Becasue this problem will grow for me as more users update to iOS 18.
I am trying to port my application over to CloudKit. My app worked fine before, but then I made scheme of changes and am trying to deploy to a new container. For some reason, the database is not being created after I create the container through Xcode.
I think I have configured the app correctly and a container was created, but no records were deployed. My app current stores data locally on individual devices just fine but they don't sync with each other. That's why I would like to use CloudKit.
See screenshot from Xcode of where I have configured the container. I also have background notifications enabled. Also see screenshot from console where the container has been created, but no records have been.
Any suggestions would be greatly appreciated.
Thank you
I've got an application built on top of SwiftData (+ CloudKit) which is published to App Store.
I've got a problem where on each app update, the data saved in the database is duplicated to the end user.
Obviously this isn't wanted behaviour, and I'm really looking forward to fixing it. However, given the restrictions of SwiftData, I haven't found a single fix for this.
The data duplication happens automatically on the first initial sync after the update. My guess is that it's because it doesn't detect the data already in the device, so it pulls all data from iCloud and appends it to the database where data in reality exists.
Hi, I'm working on a macOS app that utilizes SwiftData to save some user generated content to their private databases.
It is not clear to me at which point the app I made starts using the production database. I assumed that if I produce a Release build that it will be using the prod db, but that doesn't seem to be the case.
I made the mistake of distributing my app to users before "going to prod" with CloudKit. So after I realized what I had done, I inspected my CloudKit dashboard and records and I found the following:
For my personal developer account the data is saved in the Developer database correctly and I can inspect it.
When I use the "Act as iCloud account" feature and use one of my other accounts to inspect the data, I notice that for the other user, the data is neither in the Development environment nor the Production environment. Which leads me to believe it is only stored locally on that user's machine, since the app does in fact work, it's just not syncing with other devices of the same user.
So, my question is: how do I "deploy to production"?
I know that there is a Deploy Schema Changes button in the CloudKit dashboard. At which point should I press that? If I press it now, before distributing a new version of my app, will that somehow "signal" the already running apps on user's machines to start using the Production database?
Is there a setting in Xcode that I need to check for my Release build, so that the app does in fact start using the production db?
Is there a way to detect in the code whether the app is using the Production database or not? It would be useful so I can write appropriate migration logic, since I don't want to loose existing data users already have saved locally.
Something I want to know
and all users of CKSyncEngine care about
I'm going to build a full stack solution using CKSyncEngine, but what's the near future and the support and maintenance priorities inside Apple for CKSyncEngine?
There is only one short video for CKSyncEngine, in 2023, no updates after that, no future plans mentioned. I'm worried that this technology be deprecated or not activity maintained. This is a complex technology, without being activity maintained (or open-sourced) there will be fatal production issues we the developer cannot solve.
The CK developer in the video stated that "many apps" were using the framework, but he did not list any. The only named is NSUbiquitousKeyValueStore, but NSUbiquitousKeyValueStore is too simple a use case. I wonder is apple's Notes.app using it, or going to use it? Is SwiftData using it?
API Problems
The API design seems a little bit tricky, not designed from a user's perspective.
handleEvent doesn't contain any context information about which batch. How do I react the event properly? Let's say our sync code and CKSyncEngine, and callbacks are all on a dedicated thread.
Consider this:
in nextRecordZoneChangeBatch you provided a batch of changes, let's call this BATCH 1, including an item in database with uuid "***" and name "yyy"
before the changes are uploaded, there are new changes from many OTHER BACKGROUND THREADS made to the database. item "***"'s name is now "zzz"
handle SentRecordZoneChanges event: I get records that uploaded or failed, but I don't know which BATCH the records belong to.
How do I decide if i have to mark "***" as finished uploading or not? If I mark *** as finished that's wrong, the new name "zzz" is not uploaded.
I have to compare every field of *** with the savedRecord to decide if I finished uploading or not? That is not acceptable as the performance and memory will be bad.
Other questions
I have to add recordIDs to state, and wait for the engine to react. I don't think this is a good idea, because recordID is a CloudKit concept, and what I want to sync is a local database. I don't see any rational for this, or not documented. If the engine is going to ask for a batch of records, you can get all record ids from the batch?
I have integrated CloudKit into a CoreData application and am ready to deploy the schema to production but keep getting an "internal error" when trying to deploy to production or reset my CloudKit environment. I have attached images of what I am seeing including one of the console error. Is there any way to resolve this?