CloudKit CKShare userDidAcceptCloudKitShareWith Never Fires on Mac App

1.4k views Asked by At

I am working on accepting a CKShare in a macOS app in Swift 4. I've already done all the following:

  • Create the CKShare and save it with its rootRecord to CloudKit
  • Add a participant (CKShare.Participant)
  • I've confirmed that the CKShare is on the CloudKit server and that the person I invited has access to it. Here's a screenshot: https://d.pr/i/0sMFQq

When I click the share link associated with the CKShare, it opens my app, but nothing happens and userDidAcceptCloudKitShareWith doesn't fire.

func application(_ application: NSApplication, userDidAcceptCloudKitShareWith metadata: CKShareMetadata) {
  print("Made it!") //<-- This never gets logged :(
  let shareOperation = CKAcceptSharesOperation(shareMetadatas: [metadata])
  shareOperation.qualityOfService = .userInteractive

  shareOperation.perShareCompletionBlock = {meta, share, error in
    print("meta \(meta)\nshare \(share)\nerror \(error)")
  }
  shareOperation.acceptSharesCompletionBlock = { error in
    if let error = error{
      print("error in accept share completion \(error)")
    }else{
      //Send your user to where they need to go in your app
      print("successful share:\n\(metadata)")
    }
  }

  CKContainer.default().add(shareOperation)
}

Is there some kind of URL scheme I have to include in my info.plist? Or perhaps a protocol I need to conform to in my NSApplicationDelegate delegate? I can't, for the life of me, figure out what to do. Thanks in advance!

Update

I've tried a few more things on this. When I open the share link in a web browser, I see this:

enter image description here

Clicking OK makes the screen fade away to this:

Nothing

Not particularly helpful. :) After doing this, the participant's status in CloudKit is still Invited, so the share still hasn't been accepted.

When I click on a share link within Messages, I am shown a popup like this: CloudKit Sharing Popup After I click open, a new copy of my app shows up in the dock, then the app suddenly closes. The crash log states:

Terminating app due to uncaught exception 'CKException', reason: 'The application is missing required entitlement com.apple.developer.icloud-services'

I've tried turning iCloud off and on again in the Capabilities section of Xcode, but nothing changes. I know this exception can't be right because I can start my app normally and use CloudKit all day long. Only the CKShare causes this crash.

This is a mess. Save me, Obi-wan Kenobi, you're my only hope.

5

There are 5 answers

0
iphaaw On

You need to create the app delegate for your SwiftUI app using @NSApplicationDelegateAdaptor:

@main
struct Sharing_ServiceApp: App
{
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene
    {
        WindowGroup
        {
            ContentView()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
    }
}

I put that line in and my code instantly started receiving the share requests.

6
Clifton Labrum On

I finally got it to work! I did all of the following:

  1. Deleted my app from ~/Library/Developer/Excode/DerivedData
  2. Made sure I had no other copies of my app archived anywhere on my machine.
  3. Said a prayer.
  4. Rebooted.

Sheesh, that was rough. :)

3
user3069232 On

Yes,

You need to add this to your info.plist.

<key>CKSharingSupported</key>
<true/>

** EDITED ANSWER **

I use this code to share, I don't do it manually... not sure if this is an option under OS X I must confess. I am using iOS.

let share = CKShare(rootRecord: record2S!)
share[CKShareTitleKey] = "My Next Share" as CKRecordValue
share.publicPermission = .none

let sharingController = UICloudSharingController(preparationHandler: {(UICloudSharingController, handler:
                    @escaping (CKShare?, CKContainer?, Error?) -> Void) in
let modifyOp = CKModifyRecordsOperation(recordsToSave:
                        [record2S!, share], recordIDsToDelete: nil)
modifyOp.savePolicy = .allKeys
modifyOp.modifyRecordsCompletionBlock = { (record, recordID,
                        error) in
                        handler(share, CKContainer.default(), error)
                    }
                    CKContainer.default().privateCloudDatabase.add(modifyOp)
                })
sharingController.availablePermissions = [.allowReadWrite,
                                                          .allowPrivate]
sharingController.delegate = self
                sharingController.popoverPresentationController?.sourceView = self.view
            DispatchQueue.main.async {
                self.present(sharingController, animated:true, completion:nil)
            }

This presents an activity controller in which you can choose say email and then send a link. You might also want to watch this video, focus on cloudKit JS right at the beginning.

Watch this WWDC video too https://developer.apple.com/videos/play/wwdc2015/710/ It talks about the cloudkit JSON API, using it you can query what has and what hasn't been shared in a terminal window/simple script perhaps. I did the same when using dropbox API a few years back. Hey you can even use the cloudkit JSON API within your code in place of the native calls.

0
Oletha On

If you are using a SceneDelegate, implement the delegate callback there, instead of on AppDelegate.

func windowScene(_ windowScene: UIWindowScene, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) {
    // ...
}
1
LenK On

If your app is a Mac Catalyst app running on any version of macOS Catalina at least up to and including 10.15.4 Beta 1, a UIApplicationDelegate userDidAcceptCloudKitShareWith method will never be invoked.

After some significant debugging, we discovered that the MacCatalyst UIKit doesn’t even have an implementation for userDidAcceptCloudKitShareWithMetadata in its UIApplication delegate. It’s not broken, it’s just not there. So, at least temporarily, our workaround is the following, which seems to work, even if it’s very inelegant:

// Add CloudKit sharing acceptance handling to UINSApplicationDelegate, which is missing it.
#if targetEnvironment(macCatalyst)
extension NSObject {
    @objc func application(_ application: NSObject, userDidAcceptCloudKitShareWithMetadata cloudKitShareMetadata: CKShare.Metadata) {
        YourClass.acceptCloudKitShare(cloudKitShareMetadata: cloudKitShareMetadata)
    }
}
#endif