Search code examples
swiftuilocationmapkitcore-location

Swiftui + Core Location: Map fails to center on user


I want to build a view with a map centered on the user location when loaded. I managed to build this, but sometimes the map loads with latitude 0, longitude: 0. This happens when I move too fast between views (there are other views in the project besides the map).

It feels like the user location is loaded too slow and the Map appears with default coordinates, but I really have no idea what I'm doing wrong. Any ideas?

Map view:

import SwiftUI
import MapKit

struct MapView: View {
    @StateObject var locationManager = LocationManager()
    @State var trackingMode: MapUserTrackingMode = .follow


    var body: some View {
        Map(coordinateRegion: $locationManager.region, interactionModes: .all, showsUserLocation: true, userTrackingMode: $trackingMode)
    }
}

Location View Model:

import SwiftUI
import CoreLocation
import MapKit

class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
    @Published var region = MKCoordinateRegion()
    private let manager = CLLocationManager()
    override init() {
        super.init()
        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyBest
        manager.requestWhenInUseAuthorization()
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        locations.last.map {
            let center = CLLocationCoordinate2D(latitude: $0.coordinate.latitude, longitude: $0.coordinate.longitude)
            let span = MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
            region = MKCoordinateRegion(center: center, span: span)
        }
    }
}

Solution

  • That is exactly your problem. Location data will ALWAYS lag, just like any other retrieved data. What you need to consider is a mechanism to update your views when you get updates.

    The best way to do that is to import Combine in your LocationManager class and use a PassthroughSubject like this:

    let objectWillChange = PassthroughSubject<Void, Never>()
    @Published var region = MKCoordinateRegion() {
        willSet { objectWillChange.send() }
    }
    

    That allows you to subscribe to your publisher in the map and get updates. You will find many tutorials regarding this.