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.
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.