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

iOS14下如何将SCNView作为ARView的子视图使用?

ARKit-CoreLocation与RealityKit ARView冲突的解决方法

问题背景

在iOS 14环境下使用ARKit-CoreLocation(ARCL)库展示POI时,遇到两个关键问题:

  • SceneLocationView作为SCNView子类,添加ARView作为其子视图后,会覆盖ARView的内容
  • SceneLocationView会自动创建新的ARSession,导致原ARView的会话被置于后台,无法正常运行

核心原因

SceneLocationView内部默认会初始化并启动自己的ARSession,同时它作为SCNView,直接将ARView作为子视图添加会导致层级覆盖,且两个AR会话同时运行会抢占系统资源,引发冲突。

解决方案

核心思路是复用同一个ARSession,并调整视图层级,让ARView作为底层视图,SceneLocationView叠加在上方,同时禁用SceneLocationView的自动会话管理,改用我们自己的ARSession。

修改后的代码实现

extension RealityKitViewController {
    typealias Context = UIViewControllerRepresentableContext<RealityKitViewControllerRepresentable>
}

class RealityKitViewController: UIViewController {
    let sceneLocationView = SceneLocationView()
    let arView = ARView(frame: .zero)
    let context : Context
    let pins: [Pin]
    
    var currentLocation : CLLocation? {
        return sceneLocationView.sceneLocationManager.currentLocation
    }
    
    init (_ context : Context, pins: [Pin]) {
        self.context = context
        self.pins = pins
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setupARSession() {
        // 初始化ARSession并配置
        let session = arView.session
        let config = ARWorldTrackingConfiguration()
        config.planeDetection = [.horizontal, .vertical]
        
        // 将ARSession绑定到SceneLocationView,避免它创建新会话
        sceneLocationView.sceneLocationManager.arSession = session
        
        // 启动会话
        session.run(config)
        
        // 添加引导层
        let coachingOverlay = ARCoachingOverlayView()
        coachingOverlay.session = session
        coachingOverlay.goal = .horizontalPlane
        coachingOverlay.delegate = context.coordinator
        arView.addSubview(coachingOverlay)
        
        arView.debugOptions = [.showFeaturePoints, .showAnchorOrigins, .showAnchorGeometry]
        
        // 设置会话代理
        context.coordinator.view = arView
        session.delegate = context.coordinator
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 调整视图层级:ARView作为底层,SceneLocationView叠加在上方
        arView.frame = view.bounds
        sceneLocationView.frame = view.bounds
        // 禁用SceneLocationView的自动会话管理
        sceneLocationView.sceneLocationManager.shouldStartSession = false
        // 设置SceneLocationView透明,避免遮挡ARView内容
        sceneLocationView.scene.background.contents = UIColor.clear
        
        view.addSubview(arView)
        view.addSubview(sceneLocationView)
        
        // 初始化AR会话
        setupARSession()
        // 添加POI标记
        addPins()
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        arView.frame = view.bounds
        sceneLocationView.frame = view.bounds
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        // 启动SceneLocationView的位置跟踪(无需启动它的ARSession)
        sceneLocationView.sceneLocationManager.startUpdatingLocation()
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        // 暂停位置跟踪和AR会话
        sceneLocationView.sceneLocationManager.stopUpdatingLocation()
        arView.session.pause()
    }
    
    func addPins() {
        guard let currentLocation = currentLocation, currentLocation.horizontalAccuracy < 16 else {
            return DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
                self?.addPins()
            }
        }
        
        self.pins.forEach { pin in
            guard pin.isLocation else { return }
            guard let location = pin.location else { return assertionFailure() }
            guard let image = UIImage(named: pin.image) else { return assertionFailure() }
            
            let node = LocationAnnotationNode(location : location, image: image)
            node.scaleRelativeToDistance = true
            sceneLocationView.addLocationNodeWithConfirmedLocation(locationNode: node)
        }
    }
}

struct RealityKitViewControllerRepresentable : UIViewControllerRepresentable {
    let pins = [Pin(image: "test", location: CLLocation(coordinate: CLLocationCoordinate2D(latitude: 0.03275742958, longitude: 0.32827424), altitude: 772.1489524841309), isLocation: true)]
    @Binding var arActivate : Bool
    
    func makeUIViewController(context: Context) -> RealityKitViewController {
        let viewController = RealityKitViewController(context, pins: pins)
        return viewController
    }
    
    func updateUIViewController(_ uiViewController: RealityKitViewController, context: Context) {
        // 根据arActivate状态控制AR会话
        if arActivate {
            uiViewController.arView.session.run(ARWorldTrackingConfiguration())
        } else {
            uiViewController.arView.session.pause()
        }
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
}

class Coordinator: NSObject, ARSessionDelegate {
    weak var view: ARView?
 }

关键修改点

  • 复用ARSession:将arView的session赋值给sceneLocationView.sceneLocationManager.arSession,阻止SceneLocationView创建新会话
  • 调整视图层级:把ARView作为底层视图,SceneLocationView叠加在上方,并设置SceneLocationView的背景透明,避免遮挡AR内容
  • 禁用自动会话启动:设置sceneLocationManager.shouldStartSession = false,由我们手动管理AR会话的生命周期
  • 分离会话与位置跟踪:在viewWillAppear中仅启动位置跟踪,AR会话由setupARSession统一启动

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 03:10:57