PassthroughSubject未触发事件:AR应用点击按钮无法添加立方体
AR应用点击按钮添加立方体失效问题解决
问题描述
开发了一个简单的AR应用,预期点击红色大按钮时添加立方体。点击按钮时,ARManager.shared.actionStream(PassthroughSubject类型)发送.addNewBlock枚举值,本应触发sink设置的闭包函数添加立方体,但功能未生效。
控制台输出情况:
- 打印了
subscribe to action stream,说明subscribeToActionStream()已调用且sink已设置 - 打印了
Button was pressed,说明ARManager.shared.actionStream.send(.addNewBlock)已执行 - 未打印
Sink function was called,表明PassthroughSubject的事件未被订阅者接收
问题原因
@State属性导致订阅释放:ARViewContainer中用@State修饰的cancellables和arView会在SwiftUI视图生命周期中被重新初始化,导致订阅无法持久保存。- 初始化时机错误:在
init()中调用subscribeToActionStream时,@State修饰的cancellables还未完成框架层面的初始化,订阅无法被正确存储,函数执行完毕后订阅就被释放了。
解决方案
修改ARViewContainer的实现,移除不必要的@State修饰,将订阅逻辑移到合适的时机:
import SwiftUI import RealityKit import Combine struct ContentView : View { var body: some View { VStack { ARViewContainer() .edgesIgnoringSafeArea(.all) Button { ARManager.shared.actionStream.send(.addNewBlock) print("Button was pressed") } label: { Rectangle() .fill(.red) .cornerRadius(16) .frame(width: 100, height: 100) } } } } enum ARAction { case addNewBlock case removeAll } struct ARManager { static let shared = ARManager() private init() {} let actionStream = PassthroughSubject<ARAction, Never>() } struct ARViewContainer: UIViewRepresentable { private let arView = ARView(frame: .zero) private var cancellables: Set<AnyCancellable> = [] func makeUIView(context: Context) -> ARView { subscribeToActionStream() return arView } func subscribeToActionStream() { print("subscribe to action stream") ARManager.shared.actionStream.sink { action in print("Sink function was called") switch action { case .addNewBlock: print("add new block") addCube() case .removeAll: print("remove all") arView.scene.anchors.removeAll() } } .store(in: &cancellables) } func addCube() { print("addCube was called") // 创建立方体模型 let mesh = MeshResource.generateBox(size: 0.1, cornerRadius: 0.005) let material = SimpleMaterial(color: .gray, roughness: 0.15, isMetallic: true) let model = ModelEntity(mesh: mesh, materials: [material]) // 使用世界坐标锚点快速验证(测试时可替代平面锚点) let anchor = AnchorEntity(world: SIMD3<Float>(0, 0, -0.5)) anchor.children.append(model) arView.scene.anchors.append(anchor) } func updateUIView(_ uiView: ARView, context: Context) {} } #Preview { ContentView() }
关键修改说明
- 移除
arView和cancellables的@State修饰:UIViewRepresentable的实例由自身管理,无需SwiftUI状态包装器。 - 将
subscribeToActionStream()移到makeUIView中调用:确保视图初始化完成后再订阅,此时cancellables已正确初始化,订阅可持久存储。 - 将
ARManager的actionStream改为let:避免意外重新创建Subject导致订阅失效。 - 可选调整:将立方体锚点改为世界坐标,无需等待平面检测即可快速验证功能,测试正常后可改回平面锚点。
额外注意事项
PassthroughSubject的订阅必须存储在Set<AnyCancellable>中,否则订阅会在函数执行结束后立即释放,无法接收后续事件。- SwiftUI视图的
init()方法中不要依赖@State属性,@State由SwiftUI框架在视图被托管后初始化,提前访问可能引发异常。 - 使用平面锚点时,需设备检测到真实平面才会显示立方体,测试时确保环境中有清晰平面(如桌面)。
内容的提问来源于stack exchange,提问作者Anh Tran
相关产品推荐
相关产品推荐

