I have a SwiftUI View that I am embedding in an existing UIViewController using UIHostingController. The SwiftUI view is simple, in fact I can reduce it down to this code and reproduce the issue:
let hostingController = UIHostingController(rootView: Button {
print("tapped")
} label {
Text("Tap")
}
The hostingController is added to my existing view controller as a child like this:
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(hostingController.view)
// Code to set up autolayout constraints omitted.
addChild(hostingController)
hostingController.didMove(toParent: self)
}
The button is tappable in the canvas preview, but not in the simulator or on a real device. There are no gesture recognizers or other views covering the UIHostingController's view. I tried using .onTapGesture(perform:)
instead of a Button but that didn't work either. To make things weirder, I can add a ScrollView as a subview of my SwiftUI and scrolling works. Why won't my button work?
Apparently the issue was that the parent UIViewController was using a layer transform to animate itself onto the screen. This transform totally broke all SwiftUI tap gestures. When I changed the layer transform code to just change the view's frame everything worked.
The offending transform code looked like this:
view.transform = CGAffineTransform(translationX: -300, y: 0)
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut, animations: {
self.view.transform = CGAffineTransform.identity
}
And I changed it to something like this:
view.frame.origin.x = view.frame.origin.x - 300
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut, animations: {
self.view.frame.origin.x = self.view.frame.origin.x + 300
}