Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)
            }
        })
    }
}
like image 897
Peter van Leeuwen Avatar asked Nov 06 '18 19:11

Peter van Leeuwen


2 Answers

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! : )

like image 107
Clifton Labrum Avatar answered Nov 16 '22 01:11

Clifton Labrum


I am using RxCloudKit library, here's an a code snippet of how it handles query notifications -

public func applicationDidReceiveRemoteNotification(userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
  let dict = userInfo as! [String: NSObject]
  let notification = CKNotification(fromRemoteNotificationDictionary: dict)
        
  switch notification.notificationType {
  case CKNotificationType.query:
    let queryNotification = notification as! CKQueryNotification
    self.delegate.query(notification: queryNotification, fetchCompletionHandler: completionHandler)
...

This method is called from func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void)

Before you can receive notifications, you will need to do the following -

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
    application.registerForRemoteNotifications()        
...

UPDATE: Info.plist should contain the following -

<key>UIBackgroundModes</key>
<array>
  <string>fetch</string>
  <string>remote-notification</string>
</array>
like image 1
Maxim Volgin Avatar answered Nov 16 '22 01:11

Maxim Volgin