Unable to tap through a disabled SwiftUI View to a ViewController

I'm working on a SwiftUI app where I have a ZStack that has a ViewController (wrapped in a UIViewRepresentable view) underneath and a semitransparent SwiftUI view on top. I need to be able to tap through the SwiftUI view to the ViewController, but this does not seem to work.

I've tried setting the SwiftUI view with .disabled(true) and .allowsHitTesting(false), but neither seem to resolve the issue. I've also tried using an overlay instead of a ZStack, but this did not help either.

How do I put a SwiftUI View above a ViewController and allow taps/scrolls/other gestures to pass through to the ViewController?

I made a sample project with 2 identical versions - it worked fine in the version with a SwiftUI view underneath, but did not work in the version with the ViewController underneath.

SwiftUI only version (tapping on the red view passes through correctly):

Code Block
struct SwiftUIOnlyView: View {
    var body: some View {
        ZStack {
            Color.white.onTapGesture {
                print("tapped \(Date())")
            }
            Color.red
                .disabled(true)
                .opacity(0.20)
                .frame(width: 100, height: 100, alignment: .center)
        }
    }
}


SwiftUI red view above a ViewController with a full screen button (tapping outside the red view works fine, but tapping on the red view does not pass through to the ViewController):
Code Block
struct SwiftUIAndViewControllerView: View {
    var body: some View {
        ZStack {
            ViewControllerWrapperView()
            Color.red
                .disabled(true)
                .opacity(0.20)
                .frame(width: 100, height: 100, alignment: .center)
        }
    }
}
struct ViewControllerWrapperView: UIViewControllerRepresentable {
    func makeUIViewController(context: UIViewControllerRepresentableContext<ViewControllerWrapperView>) -> ViewController {
        return ViewController()
    }
    func updateUIViewController(_ uiViewController: ViewController, context: UIViewControllerRepresentableContext<ViewControllerWrapperView>) {
    }
}
class ViewController: UIViewController {
    @IBAction func didTap(_ sender: Any) {
        print("tapped \(Date())")
    }
}

Replies

Here is a hack which seems to work for me:

Code Block swift
struct NoHitTesting: ViewModifier {
  func body(content: Content) -> some View {
    SwiftUIWrapper { content }.allowsHitTesting(false)
  }
}
extension View {
  func userInteractionDisabled() -> some View {
    self.modifier(NoHitTesting())
  }
}
struct SwiftUIWrapper<T: View>: UIViewControllerRepresentable {
  let content: () -> T
  func makeUIViewController(context: Context) -> UIHostingController<T> {
    UIHostingController(rootView: content())
  }
  func updateUIViewController(_ uiViewController: UIHostingController<T>, context: Context) {}
}

Which you can use like this to disable the background view for instance when it covers up a UIViewController, etc.

Code Block swift
Color.red.userInteractionDisabled()

I have the exact same problem. .disable() or .allowsHitTesting() have no effect if the view below is a hosted UIKit view (in my case its a hosted UICollectionView). Taps and gestures are still captured by the SwiftUI View above the collection view.

The hack posted in the answer by @chase also does not work for me.
Same here. I found out trying to put an overlay over a MapKit Map. Very sad..
Just in case someone is still working through this issue, or one close to it.

My issue was where both Views where SwiftUI Views (one an Image and the other a user interactive Form full of Sections and widgets). I wanted the image to set up a look and feel for the form being filled in.

The answer is to go around SwiftUI by adjusting your thinking and make the Image fully opaque and the Form see through at about 80% - or to taste. Then mark the Image non-interactive (.allowsHitTesting(false)) and the Form interactive (.allowsHitTesting(true)).

My implementation used View Modifiers:

// Original Form
Form { (various Widgets) }
.texturedBackground(named: "MapleWoodGrain")

extension View {

func texturedBackground(named name: String) -> some View
    {
        return self.modifier(PatternBackground(imageName: name))
    }
}

public struct PatternBackground: ViewModifier {

    var imageName : String

    public func body(content: Content) -> some View {

        ZStack {
            Image(imageName)
                .resizable(resizingMode: .tile)
                .allowsHitTesting(false)
            content
                .opacity(0.8)
                .allowsHitTesting(true)
        }
    }
}