SwiftUI将触摸事件从UIViewControllerRepresentable传递到后面的视图

16

我正在处理一个使用UIKit和SwiftUI混合的项目。目前,我有一个ZStack,其中包含我的SwiftUI内容,我需要在该内容上方显示一个UIViewController。因此,我ZStack中的最后一项是UIViewControllerRepresentable。

ZStack {
   ...
   SwiftUIContent
   ...
   MyUIViewControllerRepresentative() 
}

我的UIViewControllerRepresentative是其他UIViewControllers的容器。这些子视图控制器不需要占据整个屏幕,因此UIViewControllerRepresentative具有透明背景,以便用户可以与SwiftUI内容进行交互。

我面临的问题是UIViewControllerRepresentative阻止所有触摸事件到达SwiftUI内容。

我已尝试通过覆盖UIViewControllers视图命中测试来解决问题:

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    // return nil if the touch event should propagate to the SwiftUI content
}

我甚至尝试完全删除视图控制器视图上的触摸事件:

我也尝试过使用以下方法:

view.isUserInteractionEnabled = false

即使那样也不起作用。

真的很感激任何帮助。


1
我还尝试过将我的视图控制器包装在UIViewRepresentable中,并仅暴露视图。即使这样,将视图的isUserInteractionEnabled设置为false也没有起作用。 - Nick Russell
请使用此 https://stackoverflow.com/questions/59493480/swiftui-scrollview-stops-working-in-zstack-under-rectangle-with-gradient#answer-61225965 解决方案。 - chatlanin
3个回答

8

解决方案:

我想出了一个可行的解决方案。

不要将UIViewControllerRepresentable放在ZStack中,而是创建一个自定义ViewModifier,该ViewModifier将内容值传递到UIViewControllerRepresentable中。然后,通过将其包装在UIHostingController中并将其添加为子视图控制器,在我的UIViewController中嵌入该内容。

ZStack {
    ... SwiftUI content ...
}.modifier(MyUIViewControllerRepresentative)

1
你能分享更多的细节/代码来解决这个问题吗?我也遇到了同样的问题。 - Davido
@Davido 这取决于你的使用情况,但我的视图修饰符看起来像这样:struct MyViewModifier: ViewModifier { func body(content: Content) -> some View { MyViewControllerRepresentative(innerSwiftUIContent: content) } } - Nick Russell
2
拥有完整的实现将会很有趣。虽然我原则上理解了所提出的解决方案,但我很难弄清楚诸如innerSwiftUIContent应该是什么类型之类的细节。如果您能编辑您的答案以添加更多细节,那将会更加有帮助和受到赞赏! - Clément Cardonnel

5

2
不幸的是,这并不一定有效。我有一个 SwiftUI 视图叠加在通过 SpriteView 呈现的 SpriteKit SKScene 上。即使使用 allowsHitTesting(false)disabled(true),顶部的 SwiftUI 视图仍会拦截触摸事件。 - West1

2
我找到的解决方案是将我的SwiftUI视图传递给重叠的UIViewControllerRepresentable。使这一切成为可能的方法是使涉及的所有实体都通用,并在您的可表示中使用@ViewBuilder
通过使用视图修饰符而不是在可表示中包装内容,可以使此过程更加简洁。
ContentView
struct ContentView: View {
    var body: some View {
        ViewControllerRepresentable {
            Text("SwiftUI Content")
        }
    }
}
UIViewControllerRepresentable
struct ViewControllerRepresentable<Content: View>: UIViewControllerRepresentable {

    @ViewBuilder let content: Content

    typealias UIViewControllerType = ViewController<Content>


    func makeUIViewController(context: Context) -> BottomSheetViewController<Content> {
        ViewController<Content>(content: UIHostingController(rootView: content))
    }

    func updateUIViewController(_ uiViewController: ViewController<Content>, context: Context) {
        
    }
    
}
UIViewController
import SwiftUI
import UIKit

final class ViewController<Content: View>: UIViewController {

    private let content: UIHostingController<Content>

    init(content: UIHostingController<Content>) {
        self.content = content
        super.init(nibName: nil, bundle: nil)
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        return nil
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .clear
        setupLayout()
    }

    private func setupLayout() {
        addChild(content)
        view.addSubview(content.view)
        content.didMove(toParent: self)

        // Here you can add UIKit views above your SwiftUI content while keeping it interactive

        NSLayoutConstraint.activate([
            content.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            content.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            content.view.topAnchor.constraint(equalTo: view.topAnchor),
            content.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接