You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的事件未被订阅者接收

问题原因

  1. @State属性导致订阅释放:ARViewContainer中用@State修饰的cancellables和arView会在SwiftUI视图生命周期中被重新初始化,导致订阅无法持久保存。
  2. 初始化时机错误:在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()
}

关键修改说明

  1. 移除arView和cancellables的@State修饰:UIViewRepresentable的实例由自身管理,无需SwiftUI状态包装器。
  2. 将subscribeToActionStream()移到makeUIView中调用:确保视图初始化完成后再订阅,此时cancellables已正确初始化,订阅可持久存储。
  3. 将ARManager的actionStream改为let:避免意外重新创建Subject导致订阅失效。
  4. 可选调整:将立方体锚点改为世界坐标,无需等待平面检测即可快速验证功能,测试正常后可改回平面锚点。

额外注意事项

  • PassthroughSubject的订阅必须存储在Set<AnyCancellable>中,否则订阅会在函数执行结束后立即释放,无法接收后续事件。
  • SwiftUI视图的init()方法中不要依赖@State属性,@State由SwiftUI框架在视图被托管后初始化,提前访问可能引发异常。
  • 使用平面锚点时,需设备检测到真实平面才会显示立方体,测试时确保环境中有清晰平面(如桌面)。

内容的提问来源于stack exchange,提问作者Anh Tran

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 00:40:20