I have a project using SwiftUI that requires CloudKit sharing, but I'm unable to get the UICloudSharingController
to play nice in a SwiftUI environment.
First Problem
A straight-forward wrap of UICloudSharingController
using UIViewControllerRepresentable
yields an endless spinner (see this). As has been done for other system controllers like UIActivityViewController
, I wrapped the UICloudSharingController
in a containing UIViewController
like this:
struct CloudSharingController: UIViewControllerRepresentable {
@EnvironmentObject var store: CloudStore
@Binding var isShowing: Bool
func makeUIViewController(context: Context) -> CloudControllerHost {
let host = CloudControllerHost()
host.rootRecord = store.noteRecord
host.container = store.container
return host
}
func updateUIViewController(_ host: CloudControllerHost, context: Context) {
if isShowing, host.isPresented == false {
host.share()
}
}
}
final class CloudControllerHost: UIViewController {
var rootRecord: CKRecord? = nil
var container: CKContainer = .default()
var isPresented = false
func share() {
let sharingController = shareController
isPresented = true
present(sharingController, animated: true, completion: nil)
}
lazy var shareController: UICloudSharingController = {
let controller = UICloudSharingController { [weak self] controller, completion in
guard let self = self else { return completion(nil, nil, CloudError.controllerInvalidated) }
guard let record = self.rootRecord else { return completion(nil, nil, CloudError.missingNoteRecord) }
let share = CKShare(rootRecord: record)
let operation = CKModifyRecordsOperation(recordsToSave: [record, share], recordIDsToDelete: [])
operation.modifyRecordsCompletionBlock = { saved, _, error in
if let error = error {
return completion(nil, nil, error)
}
completion(share, self.container, nil)
}
self.container.privateCloudDatabase.add(operation)
}
controller.delegate = self
controller.popoverPresentationController?.sourceView = self.view
return controller
}()
}
This allows the controller to come up normally, but...
Second Problem
Tap the close button or swipe to dismiss and the controller will disappear, but there's no notification that it's been dismissed. The SwiftUI view's @State
property that initiated presenting the controller is still true
. There's no obvious method to detect dismissal of the modal. After some experimenting, I discovered the presenting controller is the original UIHostingController
created in the SceneDelegate
. With some hackery, you can inject an object that is referenced in a UIHostingController
subclass into the CloudSharingController
. This will let you detect the dismissal and set the @State
property to false
. However, all nav bar buttons no longer function after dismissing so you could only ever tap this thing once. The rest of the scene is completely functional, but buttons in the nav bar don't respond.
Third Problem
Even if you could get the UICloudSharingController
to present and dismiss normally, tapping on any of the sharing methods (Messages, Mail, etc) makes the controller disappear with no animation and the controller for the sharing URL
doesn't come up. No crash or console messages--it just disappears.
Demo
I made a quick and dirty project on GitHub to demonstrate the issue: CloudKitSharing. It just creates a single String
and a CKRecord
to represent it using CloudKit. The interface displays the String
(a UUID
) with a single nav bar button to share it:
The Plea
Is there any way to use UICloudSharingController
in SwiftUI? Don't have the time to rebuild the project in UIKit or a custom sharing controller (I know--the price of being on the bleeding edge )