【问题标题】:SwiftUI and UICloudSharingController hate each otherSwiftUI 和 UICloudSharingController 互相讨厌
【发布时间】:2019-12-07 22:27:52
【问题描述】:

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

第一个问题

使用UIViewControllerRepresentable 直接包装UICloudSharingController 会产生一个无限旋转器(请参阅this)。正如对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
    }()
}

这可以让控制器正常启动,但是...

第二个问题

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

第三个问题

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

演示

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

恳求

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

【问题讨论】:

    标签: ios swiftui


    【解决方案1】:

    我得到了这个工作 - 最初,我将 UICloudSharingController 包装在 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)
            }
        }
    }
    

    【讨论】:

    • 抱歉耽搁了,马特。我重新配置了您的解决方案以使用 UICloudSharingControllerpreparationHandler 初始化程序,以便它可以正确创建和发送共享。您的解决方案确实解决了我的问题 #1 和 #2。我认为#3 只是我没有正确配置控制器或注意到来自CKModifyRecordsOperation 的错误。顺便说一句,这也可以用作导航栏按钮,尽管您可能需要稍微调整一下大小。谢谢!
    • 这里的CloudManager 是什么?我的构建失败,因为它是未定义的。我觉得我应该用别的东西来代替它,但我想不通。
    • 抱歉 - 这只是我用来跟踪某些基于云的功能和数据的实用程序类。在这种情况下,我使用它来获取我缓存的 CKContainer 实例。我更新了示例以删除该类,以消除混淆。
    • 该按钮仅在其为导航栏项时有效
    • 如果你为源视图创建一个随机帧,例如 (x: 0, y:0, height: infinity, width: infinity) 你可以让它显示在屏幕上。但是,这样做或使用导航栏,您无法共享 CloudKit 邀请。
    【解决方案2】:

    也许只使用 rootViewController。

    let window = UIApplication.shared.windows.filter { type(of: $0) == UIWindow.self }.first
    window?.rootViewController?.present(sharingController, animated: true)
    

    【讨论】:

    • 刚试过。通过使用 UIKit 按钮,您可以为弹出框设置适当的sourceView,您可以展示共享控制器。但是,仍然没有共享界面(将 url 附加到消息或电子邮件的模式)。此外,在第一次尝试后,应用程序锁定。主视图淡入背景,等待工作表放在前面,但工作表从不显示。 UIViewControllerRepresentable 方法也有同样的问题。
    • 我遇到了同样的行为。
    猜你喜欢
    • 2010-11-19
    • 1970-01-01
    • 2021-05-07
    • 2018-04-18
    • 1970-01-01
    • 2011-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多