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
相关产品推荐
相关产品推荐

