Notifications not being received for changes in shared Cloudkit database

124 views Asked by At

cI've got records in my shared Cloudkit database but I'm not receiving any notifications when changes are made to these records (create, update or delete) whilst the app is running. If I restart the app then I see the changes.

From my understanding I need to use CKDatabaseSubscription to register for changes made to/in the shared Cloudkit database. This is what I have so far:

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {

        application.registerForRemoteNotifications()

        // Create database subscription to look for changes
        let subscription = CKDatabaseSubscription(subscriptionID: "todoChanges")

        // Does this need to be CD_Todo?
        subscription.recordType = "Todo"

        let notificationInfo = CKSubscription.NotificationInfo()
        notificationInfo.shouldSendContentAvailable = true

        subscription.notificationInfo = notificationInfo

        // Save to server
        let operation = CKModifySubscriptionsOperation(subscriptionsToSave: [subscription], subscriptionIDsToDelete: nil)

        operation.modifySubscriptionsCompletionBlock = { subscriptions, deleted, error in
            if let error = error {
                // Handle the error.
            } else {
                // ... cache/store in UserDefaults?
            }
        }

        operation.qualityOfService = .utility

        CKContainer.default().sharedCloudDatabase.add(operation)

        // Additional bits for SceneDelegate
        let config = UISceneConfiguration(
            name        : nil,
            sessionRole : connectingSceneSession.role
        )

        config.delegateClass = SceneDelegate.self

        return config
    }

    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        print("Change made in shared database")
    }
}

With the above code I'm not sure if need to use the CoreData entity name for subscription.recordType ("Todo") or the Cloudkit name ("CD_Todo"). That being said, neither seem to work.

I've also attached the subscription to the shared database, which I presume is correct as that's where I want to monitor for changes. I can see the subscription within the 'Subscriptions' section of the iCloud dashboard so I can verify it's being saved.

In addition to the above I have "Remote notifications" selected for Background Modes. I don't think I need "Background fetch" and enabling it anyway doesn't seem to make a difference.

At this point all I'm trying to do is get notifications working. Ultimately I want the app to fetch the latest changes on receipt of a change notification, but I can't seem to get that first part working which will eventually trigger a fetch.

To test this out I've been modifying records via the dashboard web interface but I'm not seeing the "Change made in shared database" being output in Xcode. As far as I can tell the shared database is linked correctly as those changes made in the dashboard are reflect in my app next time I launch (or relaunch).

1

There are 1 answers

0
ComputerSaysNo On

So, I've solved my problem, but I shall explain the issue in full for context.

I'm building an app that utilities zone wide sharing to facilitate sharing between all records in a zone (instead of explicit records).

The reason for wanting to get notifications going was because I was only getting a one-way sync between two devices. That is, Device A (iPhone) was the owner (private database) and Device B (simulator) was the participant (shared database, with readWrite permissions to records belonging to Device A). In this scenario I was able to create/update/delete Todo entries from Device B and they would appear on Device A, but not the other way around (unless I restarted the app on Device B). This is why I went down the path of notifications - just so see if Device B was at least receiving something whenever a change was made from Device A.

However, I then thought to reverse the roles of the share so that Device A (iPhone) became the participant and Device B (simulator) the owner. In this scenario the databases were swapped in terms of Device A now using the shared database and Device B using the private database (which is handled automatically by NSPersistentCloudkitContainer). The same occurred: updates went from Device B to Device A (simulator -> iPhone) but not the other way around (iPhone -> simulator). However, as I had swapped the roles, and therefore the databases (I wasn't sure if a shared database behaved differently behind the scenes vs a private database) it was pointing to a problem with the simulator (which was starting to be a suspicion).

Anyway, I confirmed this by installing the app on my partner's device and sharing worked immediately and in both directions. Furthermore, it did not require any of the subscription notification code as shown in my example above. I don't know whether to laugh or cry as I've been stuck on this issue, going around the houses, for 3 days.

So, if anyone finds this post, and is experiencing similar issues try your setup between two physical devices (if you can) as it would appear, at least in my case, simulator will not receive change notifications when it is participating in a share. This has been highly irritating as no mention of this is made in Apple docs (as far as I'm aware) and it will respond to changes made via the Cloudkit dashboard if not participating in a share.

In terms of the rest of my app I've not really deviated from Apple's examples in setting up NSPersistentCloudkitContainer with private and shared databases (e.g. https://developer.apple.com/videos/play/wwdc2021/10015/?time=1264) but that's all I've needed to do. I've certainly not needed to create process to manually fetch and update local data which seems contrary to how they explain things but maybe CloudKit has evolved in recent years to take on more the necessary grunt work.