UIHostingController遮挡底层视图触摸事件的解决方法
问题:SwiftUI叠加视图触摸事件透传失败,底层按钮无法响应
场景与问题
我有一段简化后的代码,包含以下组件:
- ContentView1:带有按钮和计数标签的SwiftUI视图
- ContentView2:顶部为空白、底部显示文本的SwiftUI视图
- ViewController:将上述两个视图分别嵌入独立
UIHostingController,并将ContentView2叠加在ContentView1之上的标准UIKit视图控制器
当前核心问题:ContentView1中的按钮无法响应触摸,上层ContentView2的UIHostingController会拦截所有触摸事件,无法传递到底层视图。
已考量的限制
- 已设置
hosting2.view.backgroundColor = .clear,ContentView1可见但按钮仍无法点击 - 不能设置
hosting2.view.isUserInteractionEnabled = false,因为需要与ContentView2进行交互 - 无法重构为单个SwiftUI视图的
ZStack结构,这是生产代码的简化版本,调整成本极高
曾尝试的无效方案
- 实现继承自
UIView的自定义PassthroughView,重写point(inside:with:)方法 - 创建
PassthroughHostingController并在loadView中设置自定义透传视图,但该方案导致SwiftUI视图无法加载
解决方案:正确实现触摸透传的HostingController
1. 自定义触摸透传视图PassthroughView
该视图仅在SwiftUI内容存在的区域拦截触摸,空白区域自动将事件透传到底层:
class PassthroughView: UIView { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let hitView = super.hitTest(point, with: event) // 若触摸点落在空白区域(仅命中自身),返回nil让事件透传 return hitView == self ? nil : hitView } }
2. 创建适配的PassthroughHostingController
关键是要保证SwiftUI视图能正确挂载到自定义透传视图上,需在loadView中先初始化自定义视图,再调用父类方法:
class PassthroughHostingController<Content: View>: UIHostingController<Content> { override func loadView() { self.view = PassthroughView() super.loadView() // 必须调用,确保SwiftUI内容渲染到自定义视图 } }
3. 在ViewController中替换使用自定义HostingController
将加载ContentView2的UIHostingController替换为PassthroughHostingController:
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // 底层ContentView1 let hosting1 = UIHostingController(rootView: ContentView1()) addChild(hosting1) view.addSubview(hosting1.view) hosting1.view.frame = view.bounds hosting1.didMove(toParent: self) // 上层ContentView2:使用透传HostingController let hosting2 = PassthroughHostingController(rootView: ContentView2()) addChild(hosting2) view.addSubview(hosting2.view) hosting2.view.frame = view.bounds hosting2.view.backgroundColor = .clear hosting2.didMove(toParent: self) } }
原理说明
PassthroughView通过hitTest方法判断触摸目标:如果触摸点仅命中视图本身(即SwiftUI未渲染内容的空白区),则返回nil,让事件继续向下传递给底层的ContentView1;如果命中了SwiftUI的子视图(比如ContentView2的文本区域),则正常返回该子视图,保证ContentView2的交互功能正常。- 在
PassthroughHostingController的loadView中先设置自定义视图再调用父类方法,确保SwiftUI的视图层级正确挂载,解决了之前视图无法加载的问题。
内容的提问来源于stack exchange,提问作者Matt Beaney
相关产品推荐
相关产品推荐

