寻求位置权限授予后填充ViewModel的正确实现方式
解决方案
核心思路
要实现权限授予后自动加载地图标注,关键是让LocationManager和VenuesViewModel联动,确保权限通过且获取到有效位置后再触发数据加载,同时避免GeoFire的坐标精度错误。
步骤1:改造LocationManager,发布状态与位置
给LocationManager添加@Published属性,对外暴露权限状态和当前位置的变化:
import CoreLocation import Combine class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate { let manager = CLLocationManager() @Published var authorizationStatus: CLAuthorizationStatus = .notDetermined @Published var currentLocation: CLLocation? override init() { super.init() manager.delegate = self manager.requestWhenInUseAuthorization() manager.startUpdatingLocation() } func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { authorizationStatus = manager.authorizationStatus // 权限允许时启动位置更新 if [.authorizedWhenInUse, .authorizedAlways].contains(authorizationStatus) { manager.startUpdatingLocation() } } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { guard let latestLocation = locations.last else { return } currentLocation = latestLocation manager.stopUpdatingLocation() // 获取一次位置后停止,按需调整 } }
步骤2:让ViewModel监听LocationManager的变化
通过依赖注入将LocationManager传入ViewModel,监听权限和位置的组合变化,满足条件时加载数据,同时处理坐标精度问题:
import Foundation import Combine import GeoFire import FirebaseFirestore class VenuesViewModel: ObservableObject { @Published var venues: [Venue] = [] private let geoFire: GeoFire private var cancellables = Set<AnyCancellable>() init(locationManager: LocationManager) { let db = Firestore.firestore() geoFire = GeoFire(firebaseRef: db.collection("venues")) // 同时监听权限状态和当前位置 locationManager.$authorizationStatus .combineLatest(locationManager.$currentLocation) .sink { [weak self] status, location in guard let self = self, [.authorizedWhenInUse, .authorizedAlways].contains(status), let validLocation = location else { return } self.loadVenues(at: validLocation) } .store(in: &cancellables) } private func loadVenues(at location: CLLocation) { // 限制经纬度小数位到6位,避免GeoFire的精度错误 let fixedLocation = CLLocation( latitude: location.coordinate.latitude.roundTo(6), longitude: location.coordinate.longitude.roundTo(6) ) // 按范围查询标注 let radius = 1000.0 // 1公里范围,按需调整 geoFire.query(at: fixedLocation, withRadius: radius).observe(.keyEntered) { key, _ in // 从Firestore获取对应标注数据 Firestore.firestore().collection("venues").document(key).getDocument { snapshot, _ in guard let snapshot = snapshot, let venue = try? snapshot.data(as: Venue.self) else { return } DispatchQueue.main.async { self.venues.append(venue) } } } } } // 辅助方法:限制小数位 extension Double { func roundTo(_ places: Int) -> Double { let divisor = pow(10.0, Double(places)) return (self * divisor).rounded() / divisor } }
步骤3:调整环境对象注入顺序
先初始化LocationManager,再传入ViewModel,确保依赖关系正确:
var body: some Scene { WindowGroup { let locationManager = LocationManager() let venuesVM = VenuesViewModel(locationManager: locationManager) ContentView() .environmentObject(locationManager) .environmentObject(venuesVM) } }
错误原因说明
你遇到的"Precision must be less than 23"错误,是因为GeoFire对坐标精度有要求(经纬度小数位不能过多)。直接在locationManagerDidChangeAuthorization调用加载函数时,可能还未获取到有效位置,传递了无效/精度过高的坐标导致报错。现在的方案确保只有获取到有效位置并修正精度后,才调用GeoFire查询。
内容的提问来源于stack exchange,提问作者Phil
相关产品推荐
相关产品推荐

