SwiftUI中ARGeoTrackingStatus与相机跟踪状态无法显示问题
问题排查结果
状态无法正常渲染是以下几个核心问题导致的:
- 未绑定ARSession代理:代码启动ARSession后,从未将
arView.session.delegate设置为Coordinator实例,所有ARSessionDelegate的状态回调根本不会触发,拿不到任何状态更新数据。 - 跨线程更新UI违规:ARKit的代理回调、地理可用性检查回调默认都在后台子线程执行,直接在子线程修改
@Binding绑定的状态属性,无法触发SwiftUI的UI刷新机制,还可能触发运行时线程安全警告。 - 实例生命周期异常:将
ARView实例作为UIViewRepresentable结构体的存储属性,SwiftUI每次重绘视图时都会重新创建ARView实例,导致AR会话中断、上下文不一致。 - 状态逻辑遗漏:地理跟踪进入
.localized成功状态时,代码仅更新了精度字段,没有更新geoTrackingStatus的文本,用户无法看到定位成功的状态。 - 日志逻辑错误:设备不支持ARGeoTracking的分支中,错误打印了“支持地理跟踪”的日志,会误导问题排查。
修复方案
- 将ARView实例的创建移动到
makeUIView方法内,避免结构体重建导致的实例异常,同时在创建ARView后立即设置session代理为Coordinator。 - 给Coordinator添加arView属性持有ARView引用,供引导视图添加、session重置使用。
- 所有修改viewModel状态的逻辑,必须用
DispatchQueue.main.async包裹,保证在主线程更新UI状态。 - 补全
.localized状态的文本赋值,修正错误的支持性判断日志。
修正后核心代码
ARViewContainer 部分
struct ARViewContainer: UIViewRepresentable { struct ViewModel { var notAvailable = false var geoTrackingStatus = "" var cameraTrackingStatus = "" var accuracy = "" } @Binding var viewModel: ViewModel func makeUIView(context: Context) -> ARView { // 在此处创建ARView实例,保证生命周期正确 let arView = ARView(frame: .zero) // 核心:绑定session代理 arView.session.delegate = context.coordinator // 给coordinator传递arView引用 context.coordinator.arView = arView if ARGeoTrackingConfiguration.isSupported { ARGeoTrackingConfiguration.checkAvailability { available, error in // 切主线程更新状态 DispatchQueue.main.async { if !available { viewModel.notAvailable = true print("GeoTracking is not available at your location") } } } } else { // 修正错误日志 print("Your Device does not support ARGeoTracking") } let config = ARGeoTrackingConfiguration() arView.automaticallyConfigureSession = false arView.session.run(config) context.coordinator.addCoaching() return arView } func makeCoordinator() -> Coordiantor { Coordiantor(self) } func updateUIView(_ uiView: ARView, context: Context) { } }
Coordinator 部分
class Coordiantor: NSObject, ARSessionDelegate, ARCoachingOverlayViewDelegate { var parent: ARViewContainer // 新增arView引用 var arView: ARView? init(_ parent: ARViewContainer) { self.parent = parent } func addCoaching() { guard let arView = arView else { return } let coachingOverlay = ARCoachingOverlayView() coachingOverlay.autoresizingMask = [.flexibleWidth, .flexibleHeight] coachingOverlay.goal = .geoTracking coachingOverlay.session = arView.session coachingOverlay.delegate = self arView.addSubview(coachingOverlay) } public func coachingOverlayViewDidRequestSessionReset(_ coachingOverlayView: ARCoachingOverlayView) { guard let arView = arView else { return } let config = ARGeoTrackingConfiguration() arView.session.run(config, options: [.resetTracking, .removeExistingAnchors]) } func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) { // 主线程更新UI DispatchQueue.main.async { self.parent.viewModel.cameraTrackingStatus = camera.trackingState.description } } func session(_ session: ARSession, didChange geoTrackingStatus: ARGeoTrackingStatus) { // 主线程更新UI DispatchQueue.main.async { switch geoTrackingStatus.state { case .notAvailable: self.parent.viewModel.geoTrackingStatus = "Not Available" case .initializing: switch geoTrackingStatus.stateReason { case .none: self.parent.viewModel.geoTrackingStatus = "No issue reported" case .notAvailableAtLocation: self.parent.viewModel.geoTrackingStatus = "Geotracking not available at this location" case .needLocationPermissions: self.parent.viewModel.geoTrackingStatus = "Geotracking requires location permission" case .worldTrackingUnstable: self.parent.viewModel.geoTrackingStatus = "World tracking is not stable" case .waitingForLocation: self.parent.viewModel.geoTrackingStatus = "ARKit waiting for location" case .waitingForAvailabilityCheck: self.parent.viewModel.geoTrackingStatus = "ARKit waiting for availablity check" case .geoDataNotLoaded: self.parent.viewModel.geoTrackingStatus = "Geo data not loaded, please check network connection" case .devicePointedTooLow: self.parent.viewModel.geoTrackingStatus = "Camera pointed too low" case .visualLocalizationFailed: self.parent.viewModel.geoTrackingStatus = "Visual localization failed" @unknown default: break } case .localizing: switch geoTrackingStatus.stateReason { case .none: self.parent.viewModel.geoTrackingStatus = "No issue reported" case .notAvailableAtLocation: self.parent.viewModel.geoTrackingStatus = "Geotracking not available at this location" case .needLocationPermissions: self.parent.viewModel.geoTrackingStatus = "Geotracking requires location permission" case .worldTrackingUnstable: self.parent.viewModel.geoTrackingStatus = "World tracking is not stable" case .waitingForLocation: self.parent.viewModel.geoTrackingStatus = "ARKit waiting for location" case .waitingForAvailabilityCheck: self.parent.viewModel.geoTrackingStatus = "ARKit waiting for availablity check" case .geoDataNotLoaded: self.parent.viewModel.geoTrackingStatus = "Geo data not loaded, please check network connection" case .devicePointedTooLow: self.parent.viewModel.geoTrackingStatus = "Camera pointed too low" case .visualLocalizationFailed: self.parent.viewModel.geoTrackingStatus = "Visual localization failed" @unknown default: break } case .localized: // 补全状态文本 self.parent.viewModel.geoTrackingStatus = "Localized" switch geoTrackingStatus.accuracy { case .undetermined: self.parent.viewModel.accuracy = "Undetermined" case .low: self.parent.viewModel.accuracy = "Low" case .medium: self.parent.viewModel.accuracy = "Medium" case .high: self.parent.viewModel.accuracy = "High" @unknown default: break } @unknown default: break } } } }
其余扩展代码保持不变即可,修复后跟踪状态会实时在UI上刷新。
内容的提问来源于stack exchange,提问作者Frank Lan
相关产品推荐
相关产品推荐

