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

如何在不触发updateUIView的前提下将ARKit光线数据传回SwiftUI

解决ARKit光线估算值传递导致updateUIView频繁调用的问题

问题根源

UIViewRepresentable的任何属性(包括@Binding)发生变化时,SwiftUI都会自动调用updateUIView方法。你的代码中把light作为@Binding传给了ARViewContainer,而ARKit会持续更新这个值,导致每次光线估算值变化都会触发updateUIView——但实际上你根本不需要用light去修改ARView的状态,这完全是不必要的调用。

核心解决方案

把light的单向传递从ARViewContainer的属性中剥离,让Coordinator直接与视图模型交互更新光线值,避免因light变化触发updateUIView。同时优化updateUIView内的逻辑,只在真正需要更新ARView的状态(比如visualizationMode或objectToPlace变化)时执行操作。

修改后的代码示例

1. 视图模型(ViewModel)

class ARViewModel: ObservableObject {
    @Published var light: Float = 0.0
    @Published var objectToPlace: ObjectModel?
    @Published var visualizationMode: VisualizationMode = .ar // 自定义枚举,默认AR模式
}

2. 重构ARViewContainer

struct ARViewContainer : UIViewRepresentable {
    
    @ObservedObject var viewModel: ARViewModel
    // 仅保留需要用来更新ARView状态的绑定
    @Binding var objectToPlace: ObjectModel?
    @Binding var visualizationMode: VisualizationMode
    
    func makeUIView(context: Context) -> ARView {
        print("makeUIView")
        let arView = CustomArView(frame: .zero)
        context.coordinator.view = arView
        arView.session.delegate = context.coordinator
        return arView
    }
    
    func updateUIView(_ uiView: ARView, context: Context) {
        let coordinator = context.coordinator
        
        // 仅在visualizationMode真正变化时执行状态切换逻辑
        if coordinator.previousVisualizationMode != visualizationMode {
            coordinator.previousVisualizationMode = visualizationMode
            
            if visualizationMode == .model {
                uiView.session.pause()
                uiView.session.delegate = nil
                uiView.scene.anchors.removeAll()
                uiView.removeFromSuperview()
                uiView.window?.resignKey()
            } else {
                // 如果切回AR模式,可在这里重新启动session(按需添加)
                let config = ARWorldTrackingConfiguration()
                config.planeDetection = .horizontal
                config.environmentTexturing = .automatic
                config.isLightEstimationEnabled = true
                if ARWorldTrackingConfiguration.supportsSceneReconstruction(.mesh) {
                    config.sceneReconstruction = .mesh
                }
                uiView.session.run(config)
                uiView.session.delegate = coordinator
            }
        }
        
        // 仅在objectToPlace真正变化时执行模型放置逻辑
        if coordinator.previousObjectToPlace != objectToPlace {
            coordinator.previousObjectToPlace = objectToPlace
            
            guard let model = objectToPlace else { return }
            
            // 移除旧锚点
            if let anchor = uiView.scene.anchors.first(where: { $0.name == "default" }) {
                anchor.children.removeAll(preservingWorldTransforms: false)
            }
            
            // 添加新模型
            if let modelEntity = model.entity {
                modelEntity.transform.rotation = simd_quatf(real: 1.0, imag: SIMD3<Float>(0.0, 0.0, 0.0))
                modelEntity.transform.translation = SIMD3<Float>(0.0, 0.0, 0.0)
                modelEntity.transform.scale = SIMD3<Float>(0.01, 0.01, 0.01)
                
                let anchorEntity = AnchorEntity(plane: .horizontal)
                anchorEntity.name = "default"
                anchorEntity.addChild(modelEntity)
                
                modelEntity.generateCollisionShapes(recursive: true)
                uiView.installGestures([.scale, .rotation], for: modelEntity)
                uiView.scene.addAnchor(anchorEntity)
            }
        }
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(viewModel: viewModel)
    }

    class Coordinator: NSObject, ARSessionDelegate {
        var view: ARView?
        let viewModel: ARViewModel
        
        // 记录之前的状态,用于判断是否需要更新
        var previousVisualizationMode: VisualizationMode?
        var previousObjectToPlace: ObjectModel?

        init(viewModel: ARViewModel) {
            self.viewModel = viewModel
        }

        func session(_ session: ARSession, didUpdate frame: ARFrame) {
            // 直接更新ViewModel的light,不会触发ARViewContainer的属性变化
            viewModel.light = Float(frame.lightEstimate?.ambientIntensity ?? 0.0)
            
            guard let view = view else { return }
            for anchor in view.scene.anchors {
                if anchor.name == "default" && !anchor.children.isEmpty {
                    viewModel.objectToPlace = nil
                }
            }
        }
        
        func sessionShouldAttemptRelocalization(_ session: ARSession) -> Bool {
          return false
        }
    }
}

3. SwiftUI视图调用

struct ContentView: View {
    @StateObject private var vm = ARViewModel()
    
    var body: some View {
        ARViewContainer(
            viewModel: vm,
            objectToPlace: $vm.objectToPlace,
            visualizationMode: $vm.visualizationMode
        )
    }
}

额外说明

  • 剥离light的@Binding后,ARKit更新光线值只会触发ViewModel的@Published属性变化,进而更新SwiftUI视图,但不会再触发ARViewContainer的updateUIView。
  • 通过在Coordinator中记录之前的状态,即使updateUIView因其他原因被调用(比如父视图重绘),也不会重复执行不必要的ARView操作,进一步提升性能。

内容的提问来源于stack exchange,提问作者Nicola Rigoni

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 14:54:56