Post

Replies

Boosts

Views

Activity

iCloud cannot be accessed by third party App for some users
My App uses Core Data with Cloudkit when users purchased subscription (and uses only Core Data without subscription). My App should normally appear under 'Settings > [User Name] > iCloud > Apps using iCloud - Show All'; however, some users feedback that they my App cannot access iCloud (a message I displayed in my App when CKContainer.default().accountStatus is NOT available or FileManager.default.ubiquityIdentityToken == nil); and they do not see my App name under 'iCloud > Apps using iCloud - Show All'. From the screenshot that one user provided, only Apps from Apple are displayed under 'Apps using iCloud' and no third-party Apps are shown. Since the user can access to 'iCloud > Apple using iCloud', they have signed in to iCloud successfully. They have also tried to restart my App and restart the phone but the iCloud accessibility issue still remains. How is it possible that iCloud is accessible only to Apps from Apple but not to third party Apps? What can developer do to resolve iCloud accessibility issue here?
0
0
298
May ’24
How to safely create root and branch objects in a custom zone?
I encountered issues with some branch objects being assigned to multiple zones error (on iOS 17.5.1 and above). I get the errors when calling persistentContainer.shareshare(:to:completion:) and persistentContainer.persistUpdatedShare(:in:completion:). "The operation couldn't be completed. Request '89D3F62D-548D-4816-9F1B-594390BD8F70' was aborted because the mirroring delegate never successfully initialized due to error: Error Domain=NSCocoaErrorDomain Code=134060 "A Core Data error occurred." UserInfo={NSLocalizedFailureReason=Object graph corruption detected. Objects related to 'Oxa2255fdc1fa980c5 x-coredata://CB800FA2-6054-4D91-8EBC-E9E31890344F/CDChildObject/p588' are assigned to multiple zones: {l <CKRecordZonelD: 0x3026a1170; zoneName=com.apple.coredata.cloud-kit.share.5D30F204-5970-489F- BC2E-F863F1808A93, ownerName=defaultOwner>, <CKRecordZonelD: 0x302687b40; zoneName=com.apple.coredata.cloud-kit.zone, ownerName=_defaultOwner>" In my setup, I moved all my root objects into one custom zone (there is only one custom zone in my private database). In one of my root object, there are 6 'one-to-one' and 2 'one-to-many' relationships. The branch objects can contains other relationships. Create root object flow: func saveToPersistent(_ object: ViewModelObject) { serialQueue.async { let context = backgroundContext() context.performAndWait { // Create new baby with its one-to-one child objects. let cdNewBaby = self.newCDBaby(object, context) if let share = self.getShareZone(.privateStore).first { self.moveToShareZone(pObjects, share: share, store: .privateStore) } CoreDataManager.single.saveContext(context) self.updateZoneNSaveContext([cdNewBaby], context: context) } // context.perform } // serialQueue.async } func backgroundContext() -> NSManagedObjectContext { let context = persistentContainer.newBackgroundContext() context.transactionAuthor = contextAuthor context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy return context } func getShareZone(_ storeType: StoreType, zoneName: String? = nil) -> [CKShare] { var shares: [CKShare] = [] do { shares = try persistentContainer.fetchShares(in: stores[storeType]) } catch { print(error) return shares } if let zoneName = zoneName { shares = shares({ $0.recordID.zoneID.zoneName == zoneName }) } return shares } func moveToShareZone(_ sharedObjects: [NSManagedObject], share: CKShare, store: StoreType) { self.persistentContainer.share(sharedObjects, to: share) { managedObjects, share, container, error in if let error = error { print(error) } else if let share = share, let store = self.stores[store] { self.persistentContainer.persistUpdatedShare(share, in: store) { (share, error) in if let error = error { print(error) } } } } } // moveToShareZone Create one-to-many relationship branch object flow: serialQueue.async { let context = self.backgroundContext() context.performAndWait { // MARK: Retrieve the Root record let pObjects = CDRootRecord.fetchRecord(rootRecord.uuidString, store: store, zoneName: zoneName, context: context) if let pRootRecord = pObjects.first { self.newCDLogContent(pRootRecord.self, viewModelObject: viewModelObject, context: context) // MARK: Save Log CoreDataManager.single.saveContext(context) } } // context.performAndWait } // serialQueue Questions: (1) Should I save a root object first before share to custom zone; or share to custom zone first before save? (I implemented save before share to zone in the past and found some issues on iOS16 where the object is not saved; and end of sharing object before save which works) (2) As I understand, if a branch record is saved under a root record, it should automatically go into the root record. Or do I have to also share the branch record to the custom zone?
1
0
347
Jul ’24
Is History Tracking in Cloudkit shared database needed?
I’ve setup the Cloudkit persistent container with private and shared database (see code below). I’ve enabled NSPersistentHistoryTrackingKey to true also for .shared database. I’ve noticed in the example from Apple that the History Tracking is only enabled in .private but not for .shared. Questions: For a CloudKit setup to sync (a) between owners’ own devices (only private database), and (b) between multiple iCloud Users through .private and .shared databases, Do I need to enable history tracking for .shared database if I want to check the remote changes in the .shared database (or is the history tracking of the .private database of the owner also accessible in the .shared database)? ======================== let APP_BUNDLE_IDENTIFIER = Bundle.main.bundleIdentifier! let APP_GROUP_IDENTIFIER = "group." + APP_BUNDLE_IDENTIFIER private func setupPersistentContainer(_ container: NSPersistentCloudKitContainer? = nil, isStartup: Bool = true) -> NSPersistentCloudKitContainer { let container = container ?? getCloudKitContainer(name: CORE_DATA_DATA_MODEL_NAME) let defaultDirectoryURL: URL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: APP_GROUP_IDENTIFIER) ?? NSPersistentCloudKitContainer.defaultDirectoryURL() let privateDataStoreURL = defaultDirectoryURL.appendingPathComponent("PrivateDataStore.store") let sharedDataStoreURL = defaultDirectoryURL.appendingPathComponent("SharedDS.store") // MARK: Private Store configuration let privateDataStoreDescription = NSPersistentStoreDescription(url: privateDataStoreURL) privateDataStoreDescription.configuration = "PrivateDataStore" // Enable lightweight migration privateDataStoreDescription.shouldInferMappingModelAutomatically = true privateDataStoreDescription.shouldMigrateStoreAutomatically = true // Turn History Tracking privateDataStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey) let logOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: CLOUDKIT_LOG_CONTAINER_ID) logOptions.databaseScope = .private privateDataStoreDescription.cloudKitContainerOptions = logOptions // turn on remote change notifications privateDataStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey) container.persistentStoreDescriptions = [privateDataStoreDescription] // MARK: Share Store configuration let sharedDataStoreDescription = NSPersistentStoreDescription(url: sharedDataStoreURL) sharedDataStoreDescription.configuration = "SharedDS" // MARK: Enable lightweight migration sharedDataStoreDescription.shouldInferMappingModelAutomatically = true sharedDataStoreDescription.shouldMigrateStoreAutomatically = true sharedDataStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey) let sharedOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: CLOUDKIT_LOG_CONTAINER_ID) sharedOptions.databaseScope = .shared sharedDataStoreDescription.cloudKitContainerOptions = sharedOptions // turn on remote change notifications sharedDataStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey) container.persistentStoreDescriptions.append(sharedDataStoreDescription) self.stores = [StoreType : NSPersistentStore]() container.loadPersistentStores(completionHandler: { [self] (storeDescription, error) in if let error = error as NSError? { print(error) } if let cloudKitContainerOptions = storeDescription.cloudKitContainerOptions { if cloudKitContainerOptions.databaseScope == .private { self.stores[.privateStore] = container.persistentStoreCoordinator.persistentStore(for: storeDescription.url ?? privateDataStoreURL) } else if cloudKitContainerOptions.databaseScope == .shared { self.stores[.sharedStore] = container.persistentStoreCoordinator.persistentStore(for: storeDescription.url ?? sharedDataStoreURL) } } else { self.stores[.privateStore] = container.persistentStoreCoordinator.persistentStore(for: storeDescription.url ?? privateDataStoreURL) } }) /// Automatically merge changes in background context into View Context /// Since we always use background context to save and viewContext to read only. The store values should always trump container.viewContext.automaticallyMergesChangesFromParent = true container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy // Create separate context for read and write container.viewContext.name = VIEW_CONTEXT_NAME container.viewContext.transactionAuthor = self.contextAuthor self.setQueryGeneration(context: container.viewContext, from: .current) return container }
1
0
413
Jul ’24