iosswiftcloudkitnsnotification

Mark Notifications as Read in iOS 11 with CloudKit


I have an app that uses CloudKit and everything worked perfectly until iOS 11.

In previous iOS versions, I used a CKQuerySubscription with NSPredicate to receive notifications whenever a user changes a specific table, matching the NSPredicate properties.

After doing so, whenever the server sent notifications, I would iterate through them, fetching its changes and afterwards marking them as READ so I would parse through them again (saving a serverToken).

Now in iOS 11 , Xcode informs me that those delegates are deprecated, and I should change them, but this is where I'm having trouble with - I cannot figure out how to do it in the non-deprecated way, for iOS 11.

Here's my code:

Saving a subscription

fileprivate func setupCloudkitSubscription() {
    let userDefaults = UserDefaults.standard
    let predicate = NSPredicate(format: /*...*/) // predicate here
    let subscription = CKQuerySubscription(recordType: "recordType", predicate: predicate, subscriptionID: "tablename-changes", options: [.firesOnRecordUpdate, .firesOnRecordCreation])
    let notificationInfo = CKNotificationInfo()
    notificationInfo.shouldSendContentAvailable = true // if true, then it will push as a silent notification
    subscription.notificationInfo = notificationInfo

    let publicDB = CKContainer.default().publicCloudDatabase
    publicDB.save(subscription) { (subscription, err) in
        if err != nil {
            print("Failed to save subscription:", err ?? "")
            return
        } 
    }       
}

Check for pending notifications

fileprivate func checkForPendingNotifications() {
    let serverToken = UserDefaults.standard.pushNotificationsChangeToken
    let operation = CKFetchNotificationChangesOperation(previousServerChangeToken: serverToken)

    var notificationIDsToMarkRead = [CKNotificationID]()
    operation.notificationChangedBlock = { (notification) -> Void in
        if let notificationID = notification.notificationID {
            notificationIDsToMarkRead.append(notificationID)
        }
    }
    operation.fetchNotificationChangesCompletionBlock = {(token, err) -> Void in
        if err != nil {
            print("Error occured fetchNotificationChangesCompletionBlock:", err ?? "")
            print("deleting existing token and refetch pending notifications")

            UserDefaults.standard.pushNotificationsChangeToken = nil
            return
        }
        let markOperation = CKMarkNotificationsReadOperation(notificationIDsToMarkRead: notificationIDsToMarkRead)
        markOperation.markNotificationsReadCompletionBlock  = { (notificationIDsMarkedRead: [CKNotificationID]?, operationError: Error?) -> Void in
            if operationError != nil {
                print("ERROR MARKING NOTIFICATIONS:", operationError ?? "")
                return
            }
        }
        let operationQueue = OperationQueue()
        operationQueue.addOperation(markOperation)
        if token != nil {
            UserDefaults.standard.pushNotificationsChangeToken = token
        }
    }
    let operationQueue = OperationQueue()
    operationQueue.addOperation(operation)
}

As you can see, the code above works perfectly on iOS until 11;

Now Xcode prompts warning on the following lines:

let operation = CKFetchNotificationChangesOperation(previousServerChangeToken: serverToken)

Warning:

'CKFetchNotificationChangesOperation' was deprecated in iOS 11.0: Instead of iterating notifications to enumerate changed record zones, use CKDatabaseSubscription, CKFetchDatabaseChangesOperation, and CKFetchRecordZoneChangesOperation

AND

let markOperation = CKMarkNotificationsReadOperation(notificationIDsToMarkRead: notificationIDsToMarkRead)

Warning:

'CKMarkNotificationsReadOperation' was deprecated in iOS 11.0: Instead of iterating notifications, consider using CKDatabaseSubscription, CKFetchDatabaseChangesOperation, and CKFetchRecordZoneChangesOperation as appropriate

I tried applying CKDatabaseSubscription but by doing so I cannot apply a NSPredicate to filter the subscription as I can do with CKQuerySubscription, and if I try to fetch and mark pending notifications as Read it shows those warnings.

What's the best approach for iOS 11 in this case? Any hint?

Thank you.


Solution

  • So as I've mentioned in the comment above in the openradar bug report , which can be found here this is a known issue in iOS 11 when fetching changes for public records and then mark those changes as read, saving the given token.

    As there's not a true solution for this issue, because Apple hasn't gave a workaround for this, or maybe not marking those delegate-functions as deprecated until a solution is given I had to go through a different path, which was the follwowing:

    I had to create a custom CKRecordZone in the Private Database, then I had to subscribe database changes in that zoneID, by doing so, whenever the user changes something in that database the desired push-notifications and/or silent-push-notifications fire as expected and then I can parse the new data.

    My issue here is that I had the User Profile in a public database, so whenever the user changed something related to him (Name, Bio, etc) it saved in CloudKit and silent notifications would fire to the user's other devices to update this data - this I can do perfectly with the private database as well - but my problem was that other users could search for app-users to follow-unfollow them and if that data is stored in the private-database it will be out of general users search scope.

    In order to overcome this I had to semi-duplicate the User Profile data. The user fetches and edits its data through the private-database, and on save it also update a semi-related table in the public-database so it is available to search for general-users.

    Until Apple allows us to fetch changes from public-database as we used to do in iOS 10 this solution will work for me temporarily.