CloudKit

RSS for tag

Store structured app and user data in iCloud containers that can be shared by all users of your app using CloudKit.

Posts under CloudKit tag

17 Posts

Post

Replies

Boosts

Views

Activity

CKShare different targets
Hello, I`m working on an app that uses CloudKit and CKShare, but the app has 2 different targets, one for professional and one for patients, and theoretically the target of the professional sends the CKShare and the target of the patient should accept, but the ckshare tries to always open the target of the profissional, I would like to know if the are any way to configure the CKShare to oppen the target od the patients
1
1
382
Nov ’24
How are items in CKSyncEngine.State.pendingDatabaseChanges removed after they haven been saved to cloud?
While reading CkSyncEngine demo project code, I don't find the code to remove items in syncEngine.state.pendingRecordZoneChanges explicitly. I suspect it might occur in two possible places: nextRecordZoneChangeBatch() or ``nextRecordZoneChangeBatch()`, but I can't figure out how it occurs. nextRecordZoneChangeBatch() has the following code: let batch = await CKSyncEngine.RecordZoneChangeBatch(pendingChanges: changes) { recordID in if let contact = contacts[recordID.recordName] { let record = contact.lastKnownRecord ?? CKRecord(recordType: Contact.recordType, recordID: recordID) contact.populateRecord(record) return record } else { // We might have pending changes that no longer exist in our database. We can remove those from the state. syncEngine.state.remove(pendingRecordZoneChanges: [ .saveRecord(recordID) ]) return nil } } (I'll ignore the syncEngine.state.remove(pendingRecordZoneChanges:) in the else clause, because it's unrelated) Could it be that CKSyncEngine.RecordZoneChangeBatch.init(pendingChanges:,recordProvider:) automatically remove a CKRecord when the recordProvider: closure returns a non-nil value? I checked its document, but it doesn't say anything about this. Thanks for any help.
0
0
350
Nov ’24
A question on account change handler code in CKSyncEngine demo project
I have a quesiton on .accountChange handler code in CKSyncEngine demo project. Below is the code in handleAccountChange(): if shouldDeleteLocalData { try? self.deleteLocalData() // This error should be handled, but we'll skip that for brevity in this sample app. } if shouldReUploadLocalData { let recordZoneChanges: [CKSyncEngine.PendingRecordZoneChange] = self.appData.contacts.values.map { .saveRecord($0.recordID) } self.syncEngine.state.add(pendingDatabaseChanges: [ .saveZone(CKRecordZone(zoneName: Contact.zoneName)) ]) self.syncEngine.state.add(pendingRecordZoneChanges: recordZoneChanges) } IMHO, when user switches account, the most important thing is to reload data from the new account's document folder. However, I can't see this is done anywhere. In above code, if shouldDeleteLocalData is false, self.appData would still hold the previous account's local data. That seems very wrong. Am I missing something? It would be best if iOS restarts all applications when user switches account. If that's not the case (I guess so, otherwise there is no point to handle .accountChange in the app), I think application should implement an API to re-initialize itself. EDIT: after looking at the code again, I realize that the following code makes sure shouldDeleteLocalData is always true when user switching accounts. So the code doesn't leak the previous account's data, though I still think it has an issue - it doesn't load the new account's data. case .switchAccounts: shouldDeleteLocalData = true shouldReUploadLocalData = false
4
0
464
Nov ’24
iOS 18 Core Data and CloudKit Sync Issue with NSPersistentCloudKitContainer
After upgrading to iOS 18, my Core Data stack using NSPersistentCloudKitContainer in a shared App Group container stopped syncing correctly. The persistent store configuration, which previously worked in iOS 17, now experiences delayed or missing sync updates between devices. Then the app freezes and writes terminal the same error detail (which I provided) too many times. The debug logs from the CloudKit mirroring delegate (NSCloudKitMirroringDelegate) show repetitive notifications but no updates in persistent history. Additionally, the persistent history tracking key appears unresponsive to local changes, causing transactions to fail in updating or syncing as expected. Key setup details: Core Data is set up within an App Group container using NSPersistentCloudKitContainer. NSPersistentHistoryTrackingKey and NSPersistentStoreRemoteChangeNotificationPostOptionKey options are set to true. Any insights into changes in iOS 18 Core Data or CloudKit handling with NSPersistentCloudKitContainer, especially around history tracking and sync delays, would be greatly appreciated. Thank you. Error Detail file:///private/var/mobile/Containers/Shared/AppGroup/BF95D309-EBE9-485E-B5CE-AA17097F7B60/[AppName]Database.sqlite CoreData: debug: CoreData+CloudKit: -[NSCloudKitMirroringDelegate managedObjectContextSaved:](3123): <NSCloudKitMirroringDelegate: 0x3032b4870>: Observed context save: <NSPersistentStoreCoordinator: 0x302694bd0> - <NSManagedObjectContext: 0x3036b1a00> CoreData: debug: CoreData+CloudKit: -[NSCloudKitMirroringDelegate remoteStoreDidChange:](3166): <NSCloudKitMirroringDelegate: 0x3032b4870>: Observed remote store notification: <NSPersistentStoreCoordinator: 0x302694bd0> - 090C4244-0101-4DEF-90D6-1260570F47A5 - <NSPersistentHistoryToken - { "090C4244-0101-4DEF-90D6-1260570F47A5" = 9; }> - Persistence.swift struct PersistenceController { let container: NSPersistentCloudKitContainer static let shared = PersistenceController() static var preview: PersistenceController = {PersistenceController()}() init() { container = NSPersistentCloudKitContainer(name: "[AppName]") // Configure CloudKit for the default container if let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.[CompanyName].[AppName]") { let storeURL = url.appendingPathComponent("[AppName]Database.sqlite") let description = container.persistentStoreDescriptions.first description?.url = storeURL description?.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey) description?.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey) container.persistentStoreDescriptions = [description].compactMap { $0 } } container.loadPersistentStores { (storeDescription, error) in if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") } } container.viewContext.automaticallyMergesChangesFromParent = true container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy } }
0
1
588
Oct ’24
Does CloudKit guarantee CKRecord.Reference is always valid?
I'm considering using CloudKit in my app (it doesn't use Core Data) and have read as many materials as I can find. I haven't fully grasped it yet and have a basic question on CKRecord.Reference. Does CloudKit guarantee CKRecord.Reference value is always valid? By valid I mean the target CkRecord pointed by the CKRecord.Reference exists in the database. Let's consider an example. Suppose there are two tables: Account and Transaction: Account Table: AccountNumber Currency Rate ------------- -------- ---- a1 USD 0.03 Transaction Table: TransactionNumber AccountNumber Amount ----------------- ------------- ------ t1 a1 20 Now suppose user does the following: User first deletes account a1 and its associated transactions t1 on device A. The device saves the change to cloud. Then user adds a new transaction t2 to account a1 on device B, before the device receives the change made in step 1 from cloud. Since a1 hasn't been deleted on device B, the operation should succeed locally. The device tries to save the change to cloud too. My questions: Q1) Will device B be able to save the change in step 2 to cloud? I hope it would fail, because otherwise it would lead to inconsistent data. But I find the following in CKModifyRecordsOperation doc (emphasis mine), which implies CloudKit allows invalid reference: During a save operation, CloudKit requires that the target record of the parent reference, if set, exists in the database or is part of the same operation; all other reference fields are exempt from this requirement. (BTW, I think the fact that, when using CloudKit, Core Data requires all relations must be optional also indicates that CloudKit can't guarantee relation is always valid, though I think that is mainly an issue on client side caused by data transfer size. The above example, however, is different in that it's an issue on cloud side - the data on cloud is inconsistent). I also find the following in the document. However, I don't think it helps in the above example, because IIUC CloudKit can only detect conflict when the changes on the same record but the changes in step 1 and step 2 are on different records. Because records can change between the time you fetch them and the time you save them, the save policy determines whether new changes overwrite existing changes. By default, the operation reports an error when there’s a newer version on the server. If the above understanding is correct, however, I don't understand why the same document has the following requirement, which implies CloudKit doesn't allow invalid reference: When creating two new records that have a reference between them, use the same operation to save both records at the same time. Q2) Suppose CloudKit allows invalid reference on cloud side (that is, device B successfully saves the change in step 2 to cloud) , I wonder what's the best practice to deal with it? I think the issue is different from the optional relation requirement in Core Data when using CloudKit, because in that case the data is consistent on cloud side and eventually the client will receive complete data. In the above example, however, the data on cloud is inconsistent so the client has to remedy it somehow (although client has little information helping it). One approach I think of is to avoid the issue in the first place. My idea is to maintain a counter in the database and requires client to increase the counter (it's not Lamport clock. BTW, is it possible to use Lamport clock in this case?) when making any change. This should help CloudKit to detect conflict (though I can't think out a good strategy on how client should deal with it. A simple one is perhaps to prompt user to select one copy). However, this approach effectively uses cloud as a centralized server, which I suspect isn't the typical way how people use CloudKit, and it requires clients to maintain local counter value in various situations. I wonder what's the typical approach? Am I missing something? Thanks for any help.
2
0
915
Oct ’24
WhatsApp is incorrectly adding a number as suffix to device name in the list of Linked devices
WhatsApp is automatically suffixing a number to the device name in the list of Linked devices shown in WhatsApp app settings. The suffixed number has no correlation to the actual number of linked devices at that time. For example, a number “2” is suffixed to the device name in list of Linked devices shown in WhatsApp, even though that computer/ device is the only one shown as a linked device. If we try to edit the device name to remove the suffixed digit, it does not move forward.  I think it has something to do with the restoration of chats from cloud, because the suffixing happens subsequent to the restoration of chats from cloud. Whatsapp does not respond to emails about this issue. Anyone else?
1
0
361
Oct ’24
CKContainer shareParticipant() 'missing' nameComponents
My App requires access to iCloud. I used to be able to get the User's name components (family+given name) using: let dummyZone = CKRecordZone (zoneName: UUID().uuidString) let dummyShare = CKShare (recordZoneID: dummyZone.zoneID) Persistence.logger.notice ("\(#function): Dummy Zone: \(dummyZone.zoneID.zoneName)") // Save the dummyZone and then the dummyShare (for/in the dummyZone) let _ = try await container.privateCloudDatabase.save (dummyZone) let _ = try await container.privateCloudDatabase.save (dummyShare) // Extract the dummyShare's owner's identity - which is 'us/me' let userIdentity = dummyShare.owner.userIdentity where the resulting userIdentity had a filled out nameComponents. Now, recently, it seems to be empty. Did something change in the interfaces? I've also tried, more directly: let userRecordID = try await container.userRecordID() let userParticipant = try await container.shareParticipant(forUserRecordID: userRecordID) let userIdentity = userParticipant.userIdentity and still nameComponents is empty. Given that my App requires iCloud, is there a way to get (familyName,givenName)?
2
0
829
Oct ’24
Thumbnails for images saved in core data
I'm trying to look at what the best way to do thumbnails for images that are saved in core data, which are being synced across multiple devices. I know I can save a lower quality version into core data, but I'm wondering if there's a better way of doing it. I've come across quick look thumbnailing which looks like what I want, but I'm not sure if it can be adapted for core data as its using file paths, whereas the images are stored in a data type property in core data. From what I can tell, I'd have to save the image locally, produce the thumbnail, then delete the local image
0
0
437
Oct ’24
ckqueryoperation in CloudKit crashing
Use CloudKit's ckqueryoperation's recordmatchedblock in Swift 6.0, which always crashes, but works fine in Swift 5: func fetchAllRecords() async throws { let predicate = NSPredicate(format: "Topics = %@", "Integrations") let query = CKQuery(recordType: "PureMList", predicate: predicate) let operation = CKQueryOperation(query: query) operation.recordMatchedBlock = { recordID, result in switch result { case .success(let record): DispatchQueue.main.async { // Ensure UI updates happen here print("Fetched record: \(record)") // Update your UI elements here } case .failure(let error): // Handle the error print("Error fetching record with ID \(recordID): \(error)") } } // Ensure you're using the correct database publicDatabase.add(operation) }
2
0
500
Oct ’24
Unexpected “OTHER” error in CloudKit despite app functioning normally
Hello everyone, I’ve recently encountered an issue where my app is working perfectly fine, but I’m seeing an “OTHER” error in the CloudKit dashboard under errors. I’ve checked the logs and there doesn’t seem to be any obvious failure or issue affecting the app’s functionality. The error doesn’t provide much detail, and I’m having trouble identifying the root cause since everything appears to be functioning as expected in the app. Has anyone else experienced this? Is this something that could be related to a server-side issue, or am I missing something on my end? Any insights or advice would be greatly appreciated! Thanks in advance!
5
2
804
Oct ’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
753
Oct ’24
How to determine model type in CKSyncEngine's nextRecordZoneChangeBatch?
I’m working on a project where I’m using CKSyncEngine to sync different types of SwiftData models, specifically User and Organization, to CloudKit. Here’s how I schedule these models to be synced: For the User model: let pendingSaves: [CKSyncEngine.PendingRecordZoneChange] = [.saveRecord(user.recordID)] syncEngine.state.add(pendingRecordZoneChanges: pendingSaves) For the Organization model: let pendingSaves: [CKSyncEngine.PendingRecordZoneChange] = [.saveRecord(organization.recordID)] syncEngine.state.add(pendingRecordZoneChanges: pendingSaves) The problem arises in my CKSyncEngineDelegate's nextRecordZoneChangeBatch method where from CKRecord.ID alone I need to create the actual CKRecord that will be synced to CloudKit. This recordID alone doesn’t provide enough information to determine 1) in which local model table I need to fetch actual data to build whole CKRecord; and 2) what to put in CKRecord.recordType - whether it’s a User or an Organization. Question: What is the best practice for passing or determining the model type (e.g., User or Organization) in nextRecordZoneChangeBatch? How should I handle this in a way that effectively differentiates between the different model types being synced? Any advice or examples would be greatly appreciated! Few ideas: embed the Model type in RecordID.recordName string, but this makes my recordNames longer (like resource_29af3932). fetch data by recordID in all local persistent storage, but this seems slow and there is constraint that User and Organization IDs should never be the same. introduce lookup table where from CKRecordID I can look up model type. Somehow extend CKRecordID to add model type field?
1
1
541
Oct ’24