我有一个使用SwiftUI的项目,该项目需要CloudKit共享,但是我无法获得UICloudSharingController
在SwiftUI环境中正常播放。
第一个问题
使用UICloudSharingController
进行UIViewControllerRepresentable
的直接换行会产生无穷的旋转器(请参阅this)。正如对其他系统 Controller (如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
}()
}
这可以使 Controller 正常启动,但是...
第二个问题
点按关闭按钮或滑动即可关闭, Controller 将消失,但不会通知您已将其关闭。启动呈现 Controller 的SwiftUI View 的
@State
属性仍然是true
。没有明显的方法可以检测出模态是否消失。经过一些试验,我发现呈现 Controller 是在UIHostingController
中创建的原始SceneDelegate
。对于某些黑客,您可以将UIHostingController
子类中引用的对象注入(inject)CloudSharingController
中。这将使您检测到解雇并将@State
属性设置为false
。但是,所有导航栏按钮在关闭后均不再起作用,因此您只能轻按一次该按钮。场景的其余部分完全可以使用,但是导航栏中的按钮没有响应。第三个问题
即使您可以正常显示和关闭
UICloudSharingController
,点击任何共享方法(消息,邮件等)也可以使 Controller 消失而没有动画,并且共享URL
的 Controller 也不会出现。没有崩溃或控制台消息-它只是消失了。演示
我在GitHub上做了一个快速而肮脏的项目来演示这个问题:CloudKitSharing。它只是使用CloudKit创建一个
String
和一个CKRecord
来表示它。该界面通过单个导航栏按钮显示String
(一个UUID
)来共享它:认罪
有什么方法可以在SwiftUI中使用
UICloudSharingController
吗?没有时间在UIKit或自定义共享 Controller 中重建项目(我知道-处于前沿的价格💩) 最佳答案
我已经开始工作了-最初,我将UICloudSharingController
包装在UIViewControllerRepresentable
中,就像您提供的链接(在构建它时引用了它一样),然后将其简单地添加到SwiftUI .sheet() View 中。这在iPhone上可行,但在iPad上却失败了,因为它需要您设置popoverPresentationController?.sourceView
,而我没有一个,因为我用SwiftUI Button触发了工作表。
回到绘图板上,我将按钮本身重新构建为UIViewRepresentable
,并且能够使用SeungUn Ham在此处建议的rootViewController技巧来呈现 View 。所有功能都可以在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)
}
}
}