VisionOS中如何在Compositor Layer内使用手势?
问题翻译
我用Metal渲染场景,将自定义渲染器传入CompositorLayer,想要实现类似这样的手势交互:
.onTapGesture { print("test") }
目前的问题是无法把CompositorLayer包裹在视图中,因此没法使用SwiftUI的手势API。我该如何获取手势API的访问权限?理想情况下,希望场景里的模型/网格能像普通视图一样响应手势。
当前代码示例:
ImmersiveSpace(id: "ImmersiveSpace") { CompositorLayer(configuration: ContentStageConfiguration()) { layerRenderer in let renderer = Renderer(layerRenderer) Context.render.layerRenderer = layerRenderer Context.state.scene = GameScene() renderer.startRenderLoop() } }.immersionStyle(selection: .constant(.full), in: .full)
我理想的代码结构类似这样:
struct Model { onTapGesture() { } }
解决方法
方法1:用UIViewRepresentable包装CompositorLayer,接入SwiftUI手势
CompositorLayer本身无法直接添加SwiftUI修饰符,但可以通过UIViewRepresentable将其包装成SwiftUI可识别的视图,从而使用手势API。
示例代码:
struct CompositorLayerView: UIViewRepresentable { func makeUIView(context: Context) -> UIView { let view = UIView() // 初始化CompositorLayer并关联到视图层 let compositorLayer = CompositorLayer(configuration: ContentStageConfiguration()) { layerRenderer in let renderer = Renderer(layerRenderer) Context.render.layerRenderer = layerRenderer Context.state.scene = GameScene() renderer.startRenderLoop() } view.layer.addSublayer(compositorLayer) compositorLayer.frame = view.bounds return view } func updateUIView(_ uiView: UIView, context: Context) { // 按需添加视图更新逻辑 } } // 在ImmersiveSpace中使用包装后的视图 ImmersiveSpace(id: "ImmersiveSpace") { CompositorLayerView() .onTapGesture { print("触发点击手势") } }.immersionStyle(selection: .constant(.full), in: .full)
方法2:结合Metal射线检测,实现模型级手势响应
如果需要精确到单个模型/网格的手势交互,仅靠视图级手势不够,得结合Metal的射线检测逻辑:
- 在SwiftUI中捕获触摸手势的屏幕坐标,传递给Metal渲染器
- 在渲染器中将屏幕坐标转换为3D射线
- 遍历场景模型,用射线与模型的包围盒/网格做碰撞检测
- 检测到碰撞后执行对应逻辑
示例代码片段:
// SwiftUI端捕获手势并传递坐标 CompositorLayerView() .onTapGesture(coordinateSpace: .global) { location in Context.render.handleTap(at: location) } // Metal渲染器中的处理逻辑 class Renderer { func handleTap(at screenLocation: CGPoint) { // 将屏幕坐标转换为3D射线(需结合相机的投影、视图矩阵实现) let ray = createRay(from: screenLocation) // 遍历场景模型做碰撞检测 for model in Context.state.scene.models { if ray.intersects(model.boundingBox) { model.handleTap() break } } } private func createRay(from screenPoint: CGPoint) -> Ray { // 实现屏幕坐标转射线的具体逻辑(根据你的相机设置调整) return Ray(origin: ..., direction: ...) } } // 模型结构体(符合你的理想结构) struct Model { var name: String var boundingBox: BoundingBox // 模型自身的点击处理方法 func handleTap() { print("\(name)被点击") } }
方法3:直接给底层视图添加UIKit手势识别器
在UIViewRepresentable的makeUIView方法中,给承载CompositorLayer的UIView添加UIKit原生手势识别器:
struct CompositorLayerView: UIViewRepresentable { func makeUIView(context: Context) -> UIView { let view = UIView() // 添加UIKit点击手势识别器 let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleTap)) view.addGestureRecognizer(tapGesture) // 初始化CompositorLayer let compositorLayer = CompositorLayer(configuration: ContentStageConfiguration()) { layerRenderer in let renderer = Renderer(layerRenderer) Context.render.layerRenderer = layerRenderer Context.state.scene = GameScene() renderer.startRenderLoop() } view.layer.addSublayer(compositorLayer) compositorLayer.frame = view.bounds return view } func makeCoordinator() -> Coordinator { Coordinator() } class Coordinator: NSObject { @objc func handleTap() { print("UIKit点击手势触发") // 可在此通知渲染器处理模型级点击 Context.render.handleTap() } } func updateUIView(_ uiView: UIView, context: Context) {} }
内容的提问来源于stack exchange,提问作者Luke Xu
相关产品推荐
相关产品推荐

