获取 SwiftUI 视图的 sourceRect,以便在 UIKit ViewController 中进行弹出框演示(通过 UIHostingController)

问题描述 投票:0回答:3

我正在使用以下模式:

  • UIViewController
  • 将 UIHostingController 作为直接子级
  • 当然还有 UIHostingController 视图作为主 VC 的子视图添加并锚定到边缘
lazy var hostingVC = UIHostingController(rootView: EDWrapperView(exportDestinationVC: self))

override func viewDidLoad() {
    super.viewDidLoad()
    self.addChild(hostingVC)
    self.view.addSubview(hostingVC.view)
    
    hostingVC.view.translatesAutoresizingMaskIntoConstraints = false
    
    hostingVC.view.backgroundColor = .systemGroupedBackground
    
    self.view.topAnchor.constraint(equalTo: hostingVC.view.topAnchor).isActive = true
    self.view.bottomAnchor.constraint(equalTo: hostingVC.view.bottomAnchor).isActive = true
    self.view.leadingAnchor.constraint(equalTo: hostingVC.view.leadingAnchor).isActive = true
    self.view.trailingAnchor.constraint(equalTo: hostingVC.view.trailingAnchor).isActive = true
    
    configureNavigation()
    
    // Theme
    ThemeManager.shared.registerForThemeChanges(observer: self)
}

您可以看到 SwiftUIView 引用了 UIKitVC 来调用函数(它有点像 ViewModel)。

在同一个 UIViewController 中,我有一个呈现弹出窗口的函数,它需要一个 sourceView 和一个 sourceRect。

func showActivityVC(sourceRect: CGRect) {
    let activityVC = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
    if let popOver = activityVC.popoverPresentationController {
        popOver.sourceView = self.view! // Pass the UIVC view as sourceView
        popOver.sourceRect = sourceRect // Source rect has been computed by GeometryReader in SwiftUI. But its not exactly at the right position
    }
}

在 SwiftUIView 中,按钮如下所示:

List {
    // Export button to iOS Share sset
    Section(header: Text("Export")) {
        GeometryReader { geometry in
            Button(action: {
                let insideFrame = geometry.frame(in: .global) // Get rect in the global view, which should be the same as the UIViewController view
                self.exportDestinationVC.presentIOSShareSheet(sourceRect: insideFrame)
            }, label: {
                HStack(alignment: .firstTextBaseline) {
                    Spacer()
                    Image(systemName: "square.and.arrow.up")
                    Text("Export")
                    Spacer()
                }
            })
            .contentShape(Rectangle())
            .position(x: geometry.size.width/2.0, y: geometry.size.height/2.0)
        }
    }
}

我使用

GeometryReader
来获取全局视图中按钮的框架,它应该与UIViewController视图相同。 它有效,但不完全正确,弹出窗口没有准确指向它应该指向的位置,有一些不准确的地方,我不知道来自哪里。

如何获取 SwiftUI 视图中 SwiftUI 按钮的确切 sourceRect?

swiftui uikit uihostingcontroller
3个回答
2
投票

我也遇到了同样的问题,几何阅读器没有提供按钮的正确 y 值,并且弹出窗口在方向更改期间飞走了。

所以,我做了以下事情: 我为 UIbutton 创建了一个 UIViewRepresentable(比如 MyButton)实例,制作了一个保存 UIbutton 的自定义协调器。

MyButton 有一个回调((UIButton) -> Void),当点击 MyButton 时,我在该回调中从协调器返回了 UIbutton,并将其设置为我的弹出窗口的 sourceView,然后呈现它,它就像一个魅力。

struct MyButton: UIViewRepresentable {
var action: (UIButton) -> Void

func makeUIView(context: Self.Context) -> UIButton {
    let uiButton = UIButton()
    context.coordinator.uiButton = uiButton
    context.coordinator.addTarget()
    
    return uiButton
}

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

func updateUIView(_ uiView: UIButton, context: Self.Context) {}

class Coordinator: NSObject {
    var parent: MyButton
    var uiButton = UIButton()

    init(_ uiView: MyButton) {
        self.parent = uiView
    }
    
    func addTarget() {
        uiButton.addTarget(self, action: #selector(tapped), for: .touchUpInside)
    }
    
    @objc func tapped() {
        self.parent.action(uiButton)
    }
}
}

1
投票

这是一个更简单的工作解决方案,不需要协调员。同时,它允许动态设置标题、字体和更改颜色。

struct ButtonWithSourceView: UIViewRepresentable {
    var title: String
    var font: UIFont
    @Binding var color: UIColor
    var action: (UIButton) -> Void

    func makeUIView(context: Self.Context) -> UIButton {
        let uiButton = UIButton()
    
        uiButton.setTitle(title, for: .normal)
        uiButton.titleLabel?.font = font
        uiButton.setTitleColor(color, for: .normal)
        let uiAction = UIAction() { _ in
            action(uiButton)
        }
        uiButton.addAction(uiAction, for: .touchUpInside)
    
        return uiButton
    }

    func updateUIView(_ uiView: UIButton, context: Self.Context) {
        uiView.setTitleColor(color, for: .normal)
    }
}

0
投票

对于任何遇到同样问题的人,根据@vomi的回答,就我而言,我创建了一个

UIViewRepresentable
来获取
sourceView
。我通常将
UIViewRepresentable
嵌入到
ZStack
中,您希望从中获取
sourceView
来呈现弹出窗口。它运行完美。

struct CustomViewWithSourceView: UIViewRepresentable {
    var onAppear: (UIView) -> Void
    
    func makeUIView(context: Self.Context) -> UIView {
        let uiView = UIView()
        DispatchQueue.main.async {
            self.onAppear(uiView)
        }
        return uiView
    }

    func updateUIView(_ uiView: UIView, context: Self.Context) {
        DispatchQueue.main.async {
            self.onAppear(uiView)
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.