Clifton Labrum
Clifton Labrum

Reputation: 14060

CloudKit CKShare URL Goes Nowhere

I have successfully saved a CKShare URL to CloudKit, and I can see that the user is INVITED in the CloudKit Dashboard. My Mac app emailed the URL to that person, but when they click it, all they see it this screen on icloud.com:

Shared

Clicking OK makes everything disappear so all you see is the background on the web page.

My understanding is that the URL is supposed to open my Mac app where it will fire userDidAcceptCloudKitShareWith in my app delegate. But it does nothing.

Could this be because my app is in development and not in the Mac App Store yet? Do I need a custom URL scheme to get it to open my app?

Documentation on this stuff is pretty sparse. I'd love any help someone can provide.

Upvotes: 2

Views: 873

Answers (1)

Clifton Labrum
Clifton Labrum

Reputation: 14060

I have since learned that you must specify a fallback URL for your CloudKit container. In cases where the app isn't installed (or isn't recognized, which seems to be the case when doing dev builds in Xcode like I am), CloudKit will forward share URL to somewhere you specify. They append the unique share ID to the URL so that you can process it on your own web page.

In the CloudKit dashboard, go to Environment Settings... and you'll see this popup:

Development Environment Settings

I have it redirect to https://myapp.com/share/?id= and on my web page where it redirects to, I do a $_GET['id'] to grab the id. I then do another redirect to my application using a custom URL scheme and pass the share ID (e.g. myapp://abc123 where abc123 is the share ID).

In my app delegate, I receive the URL like this:

func application(_ application: NSApplication, open urls: [URL]) {
  if let url = urls.first, let shareId = url.host{
    fetchShare(shareId) //<-- sharedId = abc123
  }
}

I then use CKFetchShareMetadataOperation to look up the URL of the share and CKAcceptSharesOperation to accept it like this:

func fetchShare(shareId: String){
  if let url = URL(string: "https://www.icloud.com/share/\(shareId)"){
    let operation = CKFetchShareMetadataOperation(shareURLs: [url])

    operation.perShareMetadataBlock = { url, metadata, error in
      if let metadata = metadata{
        //:::
        acceptShare(metadata: metadata)
      }
    }
    operation.fetchShareMetadataCompletionBlock = { error in
      if let error = error{
        print("fetch Share error: \(error)")
      }
    }
    CKContainer.default().add(operation)
  }
}

func acceptShare(metadata: CKShareMetadata){
  let operation = CKAcceptSharesOperation(shareMetadatas: [metadata])

  operation.acceptSharesCompletionBlock = { error in
    if let error = error{
      print("accept share error: \(error)")
    }else{
      //Share accepted!
    }
  }
  CKContainer.default().add(operation)
}

I think there are easier ways to work through this using NSItemProvider and NSSharingService, but I'm doing a lot of custom UI and wanted to have full control of the share workflow.

I hope this helps someone. :)

Upvotes: 5

Related Questions