Nicolas Miari
Nicolas Miari

Reputation: 16256

CloudKit Not Sending Update Notifications

I am trying to develop a simple messaging app to learn the basics of CloudKit.

I've got it almost figured out, except I am not able to receive notifications for record update events.

To test the app, I am running it simultaneously on the device and on a simulator.

Both instances are logged into the same iCloud account (haven't gotten around to create a dedicated account for testing...); however the app distinguishes the local user from the remote one using UUIds, so that is not a problem.

When one instance of the app wants to send a message, it creates a record and saves it to CloudKit.

I am aware that APNs is not supported on the Simulator, but if I send a message from the Simulator I can get notified on the device.

This works.


Next, I want to mark the messages as "read": That is, flag them when they are first displayed on a device that is not the one that authored them.

I do this by fetching the corresponding record, modifying it, and saving it. So, the message I sent from the device is displayed on the simulator, and flagged there as 'read'. I sync that change with cloudKit and expect the device to receive a notification:

publicDatabase.perform(query, inZoneWith: nil, completionHandler: {(records, error) in

    guard let records = records, records.count > 0 else {
        return print("Error - Message: \(message.text) by \(message.userName) NOT found on the cloud!")   
    }
    let record = records[0]

    // HERE THE RECORD IS MODIFIED LOCALLY:    
    record["readTimestamp"] = (message.readTimestamp! as NSDate)

    // Now, save it:     
    self.publicDatabase.save(record, completionHandler: {record, error in
        guard error == nil else {
            return print("Error - Message: \(message.text) by \(message.userName) could NOT be saved as read!")
        }
        print("Message: \(message.text) by \(message.userName) SAVED as read at \(message.readTimestamp!)")
    })
}) 

However, on the other end, the 'Update' notification is never received.

Is it because both instances are logged into iCloud as the same user? I find this hard to believe, since the "Create" notifications are delivered without problems.

Subscriptions for both notifications ("Message Create" and "Message Update") are registered successfully and appear listed on the CloudKit Dashboard (triggers INSERT and UPDATE).


Update: After a long time thinking what can possibly be different between my "create" subscription and my "update" subscription that could cause only one of them to fire a notification, I realized that only the "create" subscription had a notification body:

let subscription = CKQuerySubscription(recordType: "Message",
        predicate: NSPredicate(value: true),
        subscriptionID: Bundle.main.bundleIdentifier! + ".subscription.message.create",
        options: .firesOnRecordCreation
    )

    let notificationInfo = CKNotificationInfo()

    // THIS LINE MAKES ALL THE DIFFERENCE:
    notificationInfo.alertBody = "You've Got Mail!"

    subscription.notificationInfo = notificationInfo

    publicDatabase.save(subscription, completionHandler: {savedSubscription, error in

...whereas the "update" subscription did not:

    let notificationInfo = CKNotificationInfo()
    subscription.notificationInfo = notificationInfo

Once I added an alert body, the "update" notifications started arriving.

However, this is just a workaround: I need silent notifications for my read updates. It doesn't make sense to display a banner just to alert the user that his message has been read on the other end. Also, the CloudKit programming Guide does not mention this limitation.

Is there a way to subscribe with silent (i.e., empty) push notifications?


Update 2: Actually, I found this bit on the API Reference for CKNotificationInfo:

Note

If you don’t set any of the alertBody, soundName, or shouldBadge properties, the push notification is sent at a lower priority that doesn’t cause the system to alert the user.

(emphasis mine)

I still fail to see how this "lower priority that doesn’t cause the system to alert the user" is equivalent to "application(:didReceiveRemoteNotification:fetchCompletionHandler:) not being called at all".

Upvotes: 2

Views: 1069

Answers (1)

sobri
sobri

Reputation: 1685

The solution appears to be to use an info object with shouldSendContentAvailable = true, like this:

let info = CKNotificationInfo()
info.shouldSendContentAvailable = true
subscription.notificationInfo = info

That has solved it for me. It causes didReceiveRemoteNotification: to fire, without any user visible notification appearing. So it's a silent notification, as desired, and it's actually arriving, as desired.

If I leave subscription.notificationInfo nil the app is never notified of changes. But with an [effectively silent] info object I get the desired results.

Upvotes: 2

Related Questions