You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 06:18:29