SwiftUI视图可见时如何正确订阅CLLocation位置更新?
解决方案
1. 改造定位代理增加防重复逻辑
不要直接暴露CLLocationManager给外部调用启停方法,在内部增加引用计数做兜底,既避免重复调用启停,也能支持多个视图同时消费定位能力,仅当所有需要定位的视图都退出后才真正停止定位:
import CoreLocation import Combine class MyLocationDelegate: NSObject, CLLocationManagerDelegate, ObservableObject { private let manager = CLLocationManager() private var usageCounter = 0 // 对外暴露的定位启动接口 func requestLocationUpdates() { usageCounter += 1 // 仅当第一个消费者请求时才真正启动定位 guard usageCounter == 1 else { return } manager.startUpdatingLocation() } // 对外暴露的定位停止接口 func endLocationUpdates() { usageCounter -= 1 // 仅当最后一个消费者退出时才真正停止定位 guard usageCounter == 0 else { return } manager.stopUpdatingLocation() } // 原有定位代理逻辑、定位数据发布逻辑保持不变 }
2. 绑定视图可靠生命周期
全版本兼容方案
利用@StateObject的生命周期由SwiftUI严格管控的特性,通过一个内部追踪类实现可靠的初始化/销毁回调,init仅在视图真正创建时触发一次,deinit仅在视图真正销毁时触发一次,完全规避onAppear/onDisappear的乱序、重复调用问题:
struct ViewThatNeedsLocation : View { @EnvironmentObject var locationDelegate: MyLocationDelegate @StateObject private var lifecycleTracker = LifecycleTracker() var body: some View { Text("需要定位的视图内容") .onReceive(lifecycleTracker.onInit) { _ in locationDelegate.requestLocationUpdates() } .onReceive(lifecycleTracker.onDeinit) { _ in locationDelegate.endLocationUpdates() } } private class LifecycleTracker: ObservableObject { let onInit = PassthroughSubject<Void, Never>() let onDeinit = PassthroughSubject<Void, Never>() init() { onInit.send() } deinit { onDeinit.send() } } }
iOS 15+ 简化方案
如果不需要兼容iOS 15以下版本,可以直接用task修饰符,其生命周期和视图可见状态严格绑定,视图出现时启动任务,视图消失时自动取消任务:
struct ViewThatNeedsLocation : View { @EnvironmentObject var locationDelegate: MyLocationDelegate var body: some View { Text("需要定位的视图内容") .task { locationDelegate.requestLocationUpdates() // 等待任务取消信号,视图消失时会触发 try? await Task.sleep(nanoseconds: .max) locationDelegate.endLocationUpdates() } } }
内容的提问来源于stack exchange,提问作者Kyle
相关产品推荐
相关产品推荐

