iosswiftswiftuimapkitcore-location

How to make MapKit map center user location smoothly? (similar to Apple Maps)


I am trying to make a SwiftUI map app using MapKit and CoreLocation. There is an option for the map to move with the user as his location changes (move as the blue marker moves).

My problem is that as the location updates (every half second or so) the map jerks from the previous location to the next one (there is a speed up and stop) instead of a linear movement (like how it is in Apple Maps if the user moves around).

Here is the code for my View:

Map(position: $position) {
    UserAnnotation()
}
.onReceive(locationManager.$position, perform: { newValue in
    withAnimation(.smooth) {
        position = newValue
    }
})

The position is a published value coming from my LocationManager which updates in the following code:

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else { return }
        self.position = .region(MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude), span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)))
    }

I tried using withAnimation but it just makes it animate between one value and another, but it is still not linear throughout multiple value changes.


Solution

  • Instead of doing your own location updates, you setup a state variable for a MapCameraPosition using the provided userLocation(followsHeading:fallback:).

    Here's a minimal working example that shows a map and the user's current location. As the user moves, the map follows smoothly like the main Maps app.

    Be sure you add the appropriate location privacy strings in Info.plist before running this code.

    This also includes a barebones location manager class so that the app can request location access from the user.

    import MapKit
    
    struct ContentView: View {
        // set followsHeading as desired
        @State private var position: MapCameraPosition = .userLocation(followsHeading: false, fallback: .automatic)
        private var lm = LocationManager()
    
        var body: some View {
            Map(position: $position) {
                UserAnnotation()
            }
        }
    }
    
    class LocationManager: NSObject, CLLocationManagerDelegate {
        private var lm = CLLocationManager()
    
        override init() {
            super.init()
    
            lm.delegate = self
        }
    
        func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
            print(error)
        }
    
        func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
            switch manager.authorizationStatus {
                case .notDetermined:
                    manager.requestWhenInUseAuthorization()
                case .restricted:
                    print("restricted")
                case .denied:
                    print("denied")
                case .authorizedAlways:
                    print("always")
                case .authorizedWhenInUse:
                    print("in use")
                @unknown default:
                    print("other")
            }
        }
    }