SwiftUI 和 UICloudSharingController 互相讨厌

smr*_*smr 8 ios swiftui

我有一个使用 SwiftUI 的项目需要 CloudKit 共享,但我无法UICloudSharingController在 SwiftUI 环境中正常运行。

第一个问题

直接UICloudSharingController使用 using 会UIViewControllerRepresentable产生一个无限的旋转器(请参阅)。正如对其他系统控制器所做的那样UIActivityViewController,我将其包装UICloudSharingController在一个包含UIViewController如下:

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
    }()
}
Run Code Online (Sandbox Code Playgroud)

这允许控制器正常启动,但是......

第二个问题

点击关闭按钮或滑动以关闭,控制器将消失,但没有通知它已被关闭。@State启动呈现控制器的 SwiftUI 视图的属性仍然是true. 没有明显的方法来检测模态的解除。经过一些试验,我发现呈现控制器是UIHostingControllerSceneDelegate. 通过一些技巧,您可以将UIHostingController子类中引用的对象注入CloudSharingController. 这将让您检测解雇并将@State属性设置为false。但是,所有导航栏按钮在关闭后都不再起作用,因此您只能点击一次。场景的其余部分功能齐全,但导航栏中的按钮没有响应。

第三个问题

即使您可以UICloudSharingController正常显示和关闭,点击任何共享方法(消息、邮件等)会使控制器消失而没有动画,并且共享控制器URL不会出现。没有崩溃或控制台消息——它只是消失了。

演示

我在 GitHub 上做了一个快速而肮脏的项目来演示这个问题:CloudKitSharing。它只是使用 CloudKit创建一个String和 aCKRecord来表示它。界面显示String(a UUID) 和一个导航栏按钮以共享它:

CloudKit共享UI

认罪

有什么方法可以UICloudSharingController在 SwiftUI 中使用吗?没有时间在 UIKit 或自定义共享控制器中重建项目(我知道——处于前沿的代价)

Mat*_*rey 6

我得到了这个工作 - 最初,我将它包装UICloudSharingController在 a 中UIViewControllerRepresentable,很像你提供的链接(我在构建它时引用了它),然后简单地将它添加到 SwiftUI .sheet() 视图中。这在 iPhone 上有效,但在 iPad 上失败了,因为它需要您设置popoverPresentationController?.sourceView,而我没有设置,因为我使用 SwiftUI 按钮触发了工作表。

回到绘图板,我将按钮本身重建为一个UIViewRepresentable,并且能够使用 SeungUn Ham 在这里建议的 rootViewController 技巧来呈现视图。所有的工作,在 iPhone 和 iPad 上 - 至少在模拟器中。

我的按钮:

struct UIKitCloudKitSharingButton: UIViewRepresentable {
    typealias UIViewType = UIButton

    @ObservedObject
    var toShare: ObjectToShare
    @State
    var share: CKShare?

    func makeUIView(context: UIViewRepresentableContext<UIKitCloudKitSharingButton>) -> UIButton {
        let button = UIButton()

        button.setImage(UIImage(systemName: "person.crop.circle.badge.plus"), for: .normal)
        button.addTarget(context.coordinator, action: #selector(context.coordinator.pressed(_:)), for: .touchUpInside)

        context.coordinator.button = button
        return button
    }

    func updateUIView(_ uiView: UIButton, context: UIViewRepresentableContext<UIKitCloudKitSharingButton>) {

    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, UICloudSharingControllerDelegate {
        var button: UIButton?

        func cloudSharingController(_ csc: UICloudSharingController, failedToSaveShareWithError error: Error) {
            //Handle some errors here.
        }

        func itemTitle(for csc: UICloudSharingController) -> String? {
            return parent.toShare.name
        }

        var parent: UIKitCloudKitSharingButton

        init(_ parent: UIKitCloudKitSharingButton) {
            self.parent = parent
        }

        @objc func pressed(_ sender: UIButton) {
            //Pre-Create the CKShare record here, and assign to parent.share...

            let sharingController = UICloudSharingController(share: share, container: myContainer)

            sharingController.delegate = self
            sharingController.availablePermissions = [.allowReadWrite]
            if let button = self.button {
                sharingController.popoverPresentationController?.sourceView = button
            }

            UIApplication.shared.windows.first?.rootViewController?.present(sharingController, animated: true)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)