12

I have two iCloud accounts (A and B) on two different devices. From one of them (A) I share ckrecord to another one (B) like this:

let controller = UICloudSharingController { controller, preparationCompletionHandler in
                        
    let share = CKShare(rootRecord: record)
    share[CKShareTitleKey] = "title" as CKRecordValue
                        
    share[CKShareTypeKey] = "pl.blueworld.fieldservice" as CKRecordValue
    share.publicPermission = .readWrite
                        
    let modifyOperation = CKModifyRecordsOperation(recordsToSave: [record, share], recordIDsToDelete: nil)
    modifyOperation.savePolicy = .ifServerRecordUnchanged
    modifyOperation.perRecordCompletionBlock = { record, error in
        print(error?.localizedDescription ?? "")
    }
                        
    modifyOperation.modifyRecordsCompletionBlock = { records, recordIds, error in
                            
        print(share.url)
        preparationCompletionHandler(share, CloudAssistant.shared.container, error)
    }
                        
    CloudAssistant.shared.container.privateCloudDatabase.add(modifyOperation)
}
                    
controller.delegate = self
                    
UIViewController.top()?.present(controller, animated: true)

When second device (B) did accept cloudkit share I fetch record and subscribe for changes:

func application(_ application: UIApplication, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShareMetadata) {
    
    let acceptSharesOperation = CKAcceptSharesOperation(shareMetadatas: [cloudKitShareMetadata])
    acceptSharesOperation.perShareCompletionBlock = { metadata, share, error in
        
        if let error = error {
            UIAlertController.show(withMessage: error.localizedDescription)
        } else {
            
            let operation = CKFetchRecordsOperation(recordIDs: [cloudKitShareMetadata.rootRecordID])
            operation.perRecordCompletionBlock = { record, _, error in
                
                if let error = error {
                    UIAlertController.show(withMessage: error.localizedDescription)
                } else if let record = record {
                    CloudAssistant.shared.save(records: [record], recordIDsToDelete: [])
                    
                    let options: CKQuerySubscriptionOptions = [.firesOnRecordCreation, .firesOnRecordUpdate, .firesOnRecordDeletion]
                    let territorySubscription = CKQuerySubscription(recordType: "Territory", predicate: NSPredicate(value: true), options: options)
                    
                    let notificationInfo = CKNotificationInfo()
                    notificationInfo.shouldBadge = false
                    notificationInfo.shouldSendContentAvailable = true
                    
                    territorySubscription.notificationInfo = notificationInfo
                    
                    CloudAssistant.shared.sharedDatabase?.save(territorySubscription) { _, _ in }
                }
            }
            
            CloudAssistant.shared.container.sharedCloudDatabase.add(operation)
        }
    }
    acceptSharesOperation.qualityOfService = .userInteractive
    CKContainer(identifier: cloudKitShareMetadata.containerIdentifier).add(acceptSharesOperation)
}

Now from device A I successfully (I am sure about that, changes is saved in iCloud) perform an update on a record shared with others. But device B doesn't know about that, unless I fetch record manually once again.

But from the other side, it works pretty well.

If I successfully perform an update on a record shared with me (on device B) then device A magically gets a notification about change and everything is fine. What makes the difference?

How to subscribe for changes on a records shared with me?

iOS 11, Swift 4, Xcode 9.

halfer
  • 19,824
  • 17
  • 99
  • 186
Bartłomiej Semańczyk
  • 59,234
  • 49
  • 233
  • 358

1 Answers1

2

Here's my checklist for debugging subscription notifications not appearing as expected. Sounds like you may have ruled some of these out already.

  1. Make sure the app is registered for notifications
  2. Make sure notifications are enabled on the device for this app
  3. Make sure all devices are using the same container
  4. On the next app startup, read all subscriptions using fetchAllSubscriptionsWithCompletionHandler and NSLog each sub's details (especially: subscriptionID, trigger options, record type and the predicate). Verify that the expected subs exist. Verify that each sub's predicate matches expectations (in this case, compare the predicates you find on both devices).

I wasted a bunch of time debugging "missing" notifications when:

  1. My locally-built version was using the TEST environment and the TestFlight users were accessing the PROD environment. Debugging step 2 found this.
  2. When inadvertently re-using a subscription ID, thus each new sub overwrote the prior one. Debugging step 4 eventually revealed the problem

So far, these four debugging steps have helped me understand all of my "missing notification" problems. Once I understood why the notifs didn't appear, that narrowed down which block of code was responsible.

Bartłomiej Semańczyk
  • 59,234
  • 49
  • 233
  • 358
Thunk
  • 4,099
  • 7
  • 28
  • 47
  • To fetch subscriptions, you need to subscribe for changes first... and this is the case... it doesn work... I subscribe for changes, but it still doesnt get notifications from the OWNER of the record (however owner gets notificatuion from another users) – Bartłomiej Semańczyk Oct 26 '17 at 08:36
  • When you call FetchAllSubscriptions on the user's machine, how many subs are found? How many did you expect? – Thunk Oct 26 '17 at 13:58
  • What is the reason to fetch all subscriptions? They may be fetched for database. And I think I need them to shared database, right? I will check it within one day... – Bartłomiej Semańczyk Oct 27 '17 at 08:02
  • Step 4 is to validate that the subs were created properly on the user's device. – Thunk Oct 27 '17 at 16:00
  • This code doesnt work. What actually should I do to get notifications when I AM NOT THE OWNER of the record? – Bartłomiej Semańczyk Oct 30 '17 at 21:16
  • Simply this answer doenst make sense, because It is impossible to add subscription to shared container due to error `Subscription type not supported in SharedDB`. So what is the simple way to register for notifications or what to do to get those notifications? All first 4 steps are fine with my app. – Bartłomiej Semańczyk Oct 30 '17 at 21:18
  • Subscriptions are per userID. The owner seems to have created a sub and is consequently receiving notifs. You haven't directly answered my question: how many subs has the user created vs how many you expect the user to have; however, your comments imply that the user has not created their own sub. Therefore, they are not receiving notifs. Only the CloudKit IDs that have created their own subs can receive notifications. – Thunk Oct 31 '17 at 11:01
  • If you're receiving an error when the user tries to create the sub, then that's the root problem. – Thunk Oct 31 '17 at 11:04
  • This may be relevant to you: https://stackoverflow.com/questions/38945807/ckquerysubscriptions-are-not-supported-in-a-sharedclouddatabase – Thunk Oct 31 '17 at 11:11
  • Ok, so the point is: how user that IS NOT the owner of CKRecord can register for notifications of that record? – Bartłomiej Semańczyk Oct 31 '17 at 11:21
  • The same way as you create any other subscription: create a subscription operation (in this case a CKDatabaseSubscription), configure the info, and run the operation. See slide 51 of the WWDC presentation: http://devstreaming.apple.com/videos/wwdc/2016/231bhrh1z1fzrejhbz7/231/231_cloudkit_best_practices.pdf – Thunk Nov 04 '17 at 16:46
  • Then I guess you need to open a bug against Apple. A quick google search of CKDatabaseSubscription, though, shows other achieving what you're having trouble with. – Thunk Nov 04 '17 at 16:49