Search code examples
iosswiftdatabasecloudkit

How to subscribe to changes for a public database in CloudKit?


What is the best way to subscribe to a public database in CloudKit? I have a table with persons. Every person contains a name and a location. Once the location changes, the location is updated in CloudKit. That part is working fine.

But I am not able to make it work to get a notification when there is a record update.

Some example would be really helpful, as I have looked into the possible option already. I have looked into the options where I save the subscription in the database and also the CKModifySubscriptionsOperation option.

Currently, my code to subscribe looks like this:

let predicate = NSPredicate(format: "TRUEPREDICATE")
let newSubscription = CKQuerySubscription(recordType: "Person", predicate: predicate, options: [.firesOnRecordCreation, .firesOnRecordDeletion, .firesOnRecordUpdate])
let info = CKSubscription.NotificationInfo()
info.shouldSendContentAvailable = true
newSubscription.notificationInfo = info

database.save(newSubscription, completionHandler: {
      (subscription, error) in
      if error != nil {
          print("Error Creating Subscription")
          print(error)
      } else {
          userSettings.set(true, forKey: "subscriptionSaved")
      }
})

Can someone also show me how my AppDelegate should look like?

I have added the didReceiveRemoteNotification function to my AppDelegate. I also called application.registerForRemoteNotifications(). This is how my didReceiveRemoteNotification function looks like: The print is not even coming for me.

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    print("Notification!!")

    let notification = CKNotification(fromRemoteNotificationDictionary: userInfo) as? CKDatabaseNotification
    if notification != nil {
        AppData.checkUpdates(finishClosure: {(result) in
            OperationQueue.main.addOperation {
                completionHandler(result)
            }
        })
    }
}

Solution

  • Here are a few other things you can check:

    = 1 =

    Make sure the CloudKit container defined in your code is the same one you are accessing in the CloudKit dashboard. Sometimes we overlook what we selected in Xcode as the CloudKit container when we create and test multiple containers.

    = 2 =

    Check the Subscriptions tab in the CloudKit dashboard and make sure your Person subscription is being created when you launch your app. If you see it, try deleting it in the CK Dashboard and then run your app again and make sure it shows up again.

    = 3 =

    Check the logs in the CK Dashboard. They will show a log entry of type push whenever a push notification is sent. If it's logging it when you update/add a record in the CK Dashboard, then you know the issue lies with your device.

    = 4 =

    Remember that push notifications don't work in the iOS simulator. You need an actual device (or a Mac if you are making a macOS app).

    = 5 =

    Through extensive testing, I've found notifications are more reliable if you always set the alertBody even if it's blank. Like this:

    let info = CKSubscription.NotificationInfo()
    info.shouldSendContentAvailable = true
    info.alertBody = "" //This needs to be set or pushes don't always get sent
    subscription.notificationInfo = info
    

    = 6 =

    For an iOS app, my app delegate handles notifications like this:

    class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
    
      func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
        //Ask Permission for Notifications
        UNUserNotificationCenter.current().delegate = self
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound], completionHandler: { authorized, error in
          DispatchQueue.main.async {
            if authorized {
              UIApplication.shared.registerForRemoteNotifications()
            }
          }
        })
    
        return true
      }
    
      //MARK: Background & Push Notifications
      func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]{
        let dict = userInfo as! [String: NSObject]
        let notification = CKNotification(fromRemoteNotificationDictionary: dict)
    
        if let sub = notification.subscriptionID{
          print("iOS Notification: \(sub)")
        }
      }
    
      //After we get permission, register the user push notifications
      func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        //Add your CloudKit subscriptions here...
      }
    
    }
    

    Getting permission for notifications isn't required if you are only doing background pushes, but for anything the user sees in the form of a popup notification, you must get permission. If your app isn't asking for that permission, try deleting it off your device and building again in Xcode.

    Good luck! : )