Swift中环境对象状态修改未同步至位置发布器的问题求助
问题:Swift位置追踪应用中trackingState切换不生效
我是Swift新手,正在开发一款追踪用户位置并在地图上显示行进路线的应用。目前已实现将位置更新发布至CoreData数据库,并在地图上绘制路线,但当我尝试通过名为trackingState的变量切换位置发布器状态时,发现该修改未在视图中生效——尽管在代码中修改其初始值可以正常切换激活与非激活追踪状态。
模拟器现象:点击toggle按钮后,地图上仍不断新增蓝色定位点,说明位置追踪未实际停止。
相关代码
MyApp.swift
import Combine import SwiftUI @main struct MyApp: App { var cancellables = [AnyCancellable]() init() { RouteManager() .sink(receiveValue: PersistenceController.shared.add).store(in: &cancellables) } var body: some Scene { WindowGroup { ContentView() .environment(\.managedObjectContext, PersistenceController.shared.container.viewContext) } } }
ContentView.swift
@StateObject var routeManager: RouteManager = RouteManager() var body: some View { ZStack(alignment: .bottom) { switch selectedTab { case .home: HomeView() case .route: RouteView(model: model) case .trips: HomeView() } TabBar() .offset(y: model.fullScreen ? 200 : 0) } .environmentObject(routeManager) }
RouteView.swift
import SwiftUI import MapKit import CoreData struct RouteView: View { @ObservedObject var model: Model @EnvironmentObject var routeManager: RouteManager @FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Location.timestamp, ascending: true)], animation: .default) private var locations: FetchedResults<Location> @State private var region = MKCoordinateRegion( center: CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0), span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)) var body: some View { ZStack { Color("Background").ignoresSafeArea() VStack { Spacer() .frame(height: 150) Map(coordinateRegion: $region, interactionModes: [.zoom], showsUserLocation: true, userTrackingMode: .constant(.follow), annotationItems: locations) { location in MapAnnotation(coordinate: CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)) { Circle().fill(Color.blue).frame(width: 10, height: 10) } } .cornerRadius(50, corners: [.topLeft, .topRight]) .shadow(color: .primary.opacity(0.15), radius: 20, x: -5, y: -5) .edgesIgnoringSafeArea(.all) } Button { routeManager.toggleTrip() } label: { ZStack { Rectangle() .foregroundColor(.red) Text("toggle") .foregroundColor(.white) } } .frame(width: 100, height: 50) } .overlay(NavigationBar(title: "New Trip", hasScrolled: .constant(false))) } }
RouteManager.swift
import Foundation import MapKit import Combine import CoreLocation import SwiftUI class RouteManager: NSObject, ObservableObject { @Published var trackingState: TrackingState = .active private var locationManager: CLLocationManager! typealias Output = (longitude: Double, latitude: Double) typealias Failure = Never private let wrapped = PassthroughSubject<(Output), Failure>() override init() { super.init() locationManagerConfig() } private func locationManagerConfig() { locationManager = CLLocationManager() self.locationManager.delegate = self self.locationManager.desiredAccuracy = kCLLocationAccuracyBest self.locationManager.requestWhenInUseAuthorization() self.locationManager.startUpdatingLocation() } public func startRoute() { self.locationManager.requestWhenInUseAuthorization() self.locationManager.requestAlwaysAuthorization() self.locationManager.allowsBackgroundLocationUpdates = true self.trackingState = .active } public func stopRoute() { locationManager.allowsBackgroundLocationUpdates = false trackingState = .inactive } public func toggleTrip() { if trackingState == .active { stopRoute() } else { startRoute() } } } extension RouteManager: CLLocationManagerDelegate { func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { // This is the code to control whether or not to send the location updates if trackingState != .active { return } guard let location = locations.last else { return } wrapped.send((longitude: location.coordinate.longitude, latitude: location.coordinate.latitude)) } } extension RouteManager: Publisher { func receive<Downstream: Subscriber>(subscriber: Downstream) where Failure == Downstream.Failure, Output == Downstream.Input { wrapped.subscribe(subscriber) } }
TrackingState.swift
enum TrackingState { case inactive case active }
问题根源与修复方案
1. 重复创建RouteManager实例
MyApp.swift的init中新建了一个独立的RouteManager实例,而视图操作的是ContentView里的@StateObject实例,两者状态完全隔离。导致订阅CoreData的实例无法感知视图中修改的trackingState。
修复:
修改MyApp.swift,全局共享同一个RouteManager实例:
@main struct MyApp: App { var cancellables = [AnyCancellable]() @StateObject private var routeManager = RouteManager() init() { routeManager .sink(receiveValue: PersistenceController.shared.add).store(in: &cancellables) } var body: some Scene { WindowGroup { ContentView() .environment(\.managedObjectContext, PersistenceController.shared.container.viewContext) .environmentObject(routeManager) } } }
同时删除ContentView中的@StateObject var routeManager: RouteManager = RouteManager(),改为通过@EnvironmentObject获取:
struct ContentView: View { @EnvironmentObject var routeManager: RouteManager // 其余代码保持不变 }
2. 停止追踪时未终止位置更新
当前stopRoute仅关闭了后台更新权限,但未调用locationManager.stopUpdatingLocation(),位置管理器仍在持续更新坐标,只是被trackingState过滤了存入CoreData的操作,导致视觉上追踪未停止。
修复:
更新RouteManager.swift中的startRoute和stopRoute方法:
public func startRoute() { self.locationManager.requestWhenInUseAuthorization() self.locationManager.requestAlwaysAuthorization() self.locationManager.allowsBackgroundLocationUpdates = true self.locationManager.startUpdatingLocation() self.trackingState = .active } public func stopRoute() { locationManager.stopUpdatingLocation() locationManager.allowsBackgroundLocationUpdates = false trackingState = .inactive }
3. 优化建议
- 权限请求可统一放在初始化阶段,避免重复调用
- 可在视图中根据
trackingState动态修改按钮文本(如"开始追踪"/"停止追踪"),直观反馈当前状态
内容的提问来源于stack exchange,提问作者Joshua Terry
相关产品推荐
相关产品推荐

