3

I'm following the 'CloudKit Best Practices' WWDC talk about adding subscriptions, which seems to have changed in iOS10.

The code below returns a 'Success!', however my 'AllChanges' subscription never appears in Subscription Types on CloudKit Dashboard.

I'm on Xcode 8 beta 6.

    let subscription = CKDatabaseSubscription(subscriptionID:"AllChanges")
    let notificationInfo = CKNotificationInfo()
    notificationInfo.shouldSendContentAvailable = true
    subscription.notificationInfo = notificationInfo

    let operation = CKModifySubscriptionsOperation(subscriptionsToSave: [subscription], subscriptionIDsToDelete: [])
    operation.modifySubscriptionsCompletionBlock =  {
        (modifiedSubscriptions: [CKSubscription]?, deletedSubscriptionIDs: [String]?, error: Error?) -> Void in

        if error != nil {
            print(error!.localizedDescription)
        } else {
            print("Success!")
        }
    }
    operation.qualityOfService = .utility
    privateDatabase.add(operation)
William Robinson
  • 1,038
  • 17
  • 32
  • Does the subscription work? Have you tried using [CKDatabase.fetchAllSubscriptions(completionHandler:)](https://developer.apple.com/reference/cloudkit/ckdatabase/1449110-fetchallsubscriptions) to fetch all subscriptions in your privateDatabase and see if it exists? – breakingobstacles Aug 27 '16 at 16:01
  • privateDatabase.fetchAllSubscriptions { (subscription: [CKSubscription]?, error: Error?) in print("Error = \(error)") print("Subs = \(subscription)") } <- Error is nil and Subs is empty – William Robinson Aug 28 '16 at 10:22
  • 1) Have you tried printing the contents of modifiedSubscriptions in your modifySubscriptionsCompletionBlock to ensure that it isn't empty? 2.) Have you tried resetting the development environment in CloudKit Dashboard? – breakingobstacles Aug 29 '16 at 02:14
  • When I print out modifiedSubscriptions it has my AllChanges subscription inside. I reset the development environment and the subscription still doesn't add, though records upload to CloudKit fine. – William Robinson Aug 29 '16 at 13:05
  • I am seeing similar with Xcode 8 release. I can create records with no problem on the private database but the CKModifySubscriptionsOperation gets an error: () The notification registrations work, the user is asked to accept remote notifications, etc. – vagrant Oct 09 '16 at 05:38

1 Answers1

6

I have had the same problem with CKDatabaseSubscription, as have many others:

I am listing some caveats first in case they explain your issue:

  • subscriptions often do not appear in "Developer" CloudKit dashboard (they exist, but are not shown - easiest way to test is rename subscription and see if CloudKit complains about duplicate subscriptions)
  • push notifications are not sent to Simulator

Solution:

What fixed this for me was to create a custom private zone and save all my data to that zone (works in private databases only). Then I receive push notifications on any changes to that zone.

You will need to create the zone (-after- checking for CKAccountStatus = .available and -before- any record saves):

let operation = CKModifyRecordZonesOperation(recordZonesToSave: [CKRecordZone(zoneName: "MyCustomZone")], recordZoneIDsToDelete: nil)
operation.modifyRecordZonesCompletionBlock = { (savedRecordZones: [CKRecordZone]?, deletedRecordZoneIDs: [CKRecordZoneID]?, error: Error?) in
    if let error = error {
        print("Error creating record zone \(error.localizedDescription)")
    }
}
privateDatabase?.add(operation)

And then use that zone when saving your records:

let record = CKRecord(recordType: "MyRecordType", zoneID: CKRecordZone(zoneName: "MyCustomZone")) 
// you can save zone to CKRecordID instead, if you want a custom id

Then skip CKFetchDatabaseChangesOperation (because we already know our zone), and use CKFetchRecordZoneChangesOptions instead:

let options = CKFetchRecordZoneChangesOptions()
options.previousServerChangeToken = myCachedChangeToken
let operation = CKFetchRecordZoneChangesOperation(
    recordZoneIDs: [myCustomZoneId],
    optionsByRecordZoneID: [myCustomZoneId: options]
)
operation.fetchAllChanges = true
operation.recordChangedBlock = { (record: CKRecord) -> Void in
        ... do something
}
operation.recordWithIDWasDeletedBlock = { (recordId: CKRecordID, recordType: String) -> Void in
        ... do something
}
operation.recordZoneFetchCompletionBlock = { (recordZoneId, changeToken, tokenData, isMoreComing, error) in
    if let error = error {
        print("Error recordZoneFetchCompletionBlock: \(error.localizedDescription)")
        return
    }
    myCachedChangeToken = changeToken
}
privateDatabase?.add(operation)
E. Ivie
  • 91
  • 1
  • 7
  • 1
    Based on your answer here I switched from using the default zone of the private database to a custom private zone -- and that fixed it. Thank you! I never saw this limitation in the documentation tho. Anyone have a reference? – vegashacker Jan 25 '17 at 15:48
  • 1
    @vegashacker The custom zone requirement is documented, though it's easy to miss. [CloudKit Quick Start](https://developer.apple.com/library/content/documentation/DataManagement/Conceptual/CloudKitQuickStart/MaintainingaLocalCacheofCloudKitRecords/MaintainingaLocalCacheofCloudKitRecords.html#//apple_ref/doc/uid/TP40014987-CH12-SW1) says "to use the change tracking functionality of CloudKit, you need to store your app data in a custom zone in the user's private database" – Mike Mertsock Mar 10 '18 at 15:44