swiftuimapkitdriving-directions

In MapKit how would i show users location and draw a polyline to a predetermined lat, lon


EDITED: I edited my code because it was a mess and confusing, so I am trying again.

My overall goal is to get a users location and then provide directions to a lat/lon on the map.

The code below creates directions between two points but how can I make the starting point the users location?

Also.. I dont seem to be able to get the users location blue dot to appear, even though I am getting their location.

import SwiftUI
import MapKit
import CoreLocation

@Observable
class NewLocationManager {
    var location: CLLocation? = nil
    
    private let locationManager = CLLocationManager()
    
    func requestUserAuthorization() async throws {
        locationManager.requestWhenInUseAuthorization()
    }
    
    func startCurrentLocationUpdates() async throws {
        for try await locationUpdate in CLLocationUpdate.liveUpdates() {
            guard let location = locationUpdate.location else { return }

            self.location = location
            print(location)
            print("location: \(location.coordinate.latitude), \(location.coordinate.longitude)")
            let lat = location.coordinate.latitude
            let long = location.coordinate.longitude
        }
    }
}

struct ContentView: View {
    @State var newlocationManager = NewLocationManager()
    @State private var selectedResult: MKMapItem?
    @State private var route: MKRoute?
    
    private let startingPoint = CLLocationCoordinate2D(
        latitude: 38.941382,
        longitude: -122.484136
    )
    
    private let destinationCoordinates = CLLocationCoordinate2D(
        latitude: 37.94479,
        longitude: -122.50959
    )
    
    
    
    var body: some View {
        
        VStack {
            
            
            Text("New location manager: \(newlocationManager.location?.description ?? "No Location Provided!")")
        }
        Map(selection: $selectedResult) {
            // Adding the marker for the starting point
            Marker("Start", coordinate: self.startingPoint)
            
            // Show the route if it is available
            if let route {
                MapPolyline(route)
                    .stroke(.blue, lineWidth: 5)
                
                
            }
        }
        .onChange(of: selectedResult){
            getDirections()
            
        }
        
        .task {
            try? await newlocationManager.requestUserAuthorization()
            try? await newlocationManager.startCurrentLocationUpdates()
            // remember that nothing will run here until the for try await loop finishes
        }
        .onAppear {
            CLLocationManager().requestWhenInUseAuthorization()
            self.selectedResult = MKMapItem(placemark: MKPlacemark(coordinate: self.destinationCoordinates))
            
            
        }
        
    }
    
    func getDirections() {
        self.route = nil
        
        // Check if there is a selected result
        guard let selectedResult else { return }
        
        // Create and configure the request
        let request = MKDirections.Request()
        request.source = MKMapItem(placemark: MKPlacemark(coordinate: self.startingPoint))
        request.destination = self.selectedResult
        
        // Get the directions based on the request
        Task {
            let directions = MKDirections(request: request)
            let response = try? await directions.calculate()
            route = response?.routes.first
        }
    }
    
}


#Preview {
    ContentView()
}

Solution

  • To ....show users location and draw a polyline to a predetermined lat, lon, try this approach using @State private var destinationCoordinates..., an additional .onChange(of: newlocationManager.location) and a new getDirections2() to get the polyline route from the startingPoint to the use current location stored in the destinationCoordinates.

    You will have to adjust the logic/parameters in startCurrentLocationUpdates to update the location only if there is a significant change in user location.

    @Observable
    class NewLocationManager {
        var location: CLLocation? = nil
        private let locationManager = CLLocationManager()
        
        func requestUserAuthorization() async throws {
            locationManager.requestWhenInUseAuthorization()
        }
        
        func startCurrentLocationUpdates() async throws {
            for try await locationUpdate in CLLocationUpdate.liveUpdates() {
                // adjust the logic/parameters as you require
                let oldLocation = self.location == nil ? CLLocation() : self.location!
                guard let newLocation = locationUpdate.location else { return }
                if !oldLocation.isClose(to: newLocation, withinDistance: 50.0) {
                    self.location = newLocation
                }
            }
        }
    }
    
    struct ContentView: View {
        @State var newlocationManager = NewLocationManager()
        @State private var selectedResult: MKMapItem?
        @State private var route: MKRoute?
        
        // for my tests, Tokyo garden
        private let startingPoint = CLLocationCoordinate2D(latitude: 35.661991, longitude: 139.762735)
        
        @State private var destinationCoordinates = CLLocationCoordinate2D(latitude: 35.67, longitude: 139.763)  // <--- here @State
        
        var body: some View {
            Map(selection: $selectedResult) {
                UserAnnotation()
                // Adding the marker for the starting point
                Marker("Start", coordinate: startingPoint)
                // Show the route if it is available
                if let route {
                    MapPolyline(route)
                        .stroke(.blue, lineWidth: 5)
                }
            }
            .onChange(of: newlocationManager.location) {  // <--- here
                if let coord = newlocationManager.location?.coordinate {
                    destinationCoordinates = coord
                    getDirections2()
                }
            }
            .task {
                try? await newlocationManager.requestUserAuthorization()
                try? await newlocationManager.startCurrentLocationUpdates()
                // remember that nothing will run here until the for try await loop finishes
            }
            .onAppear {
                CLLocationManager().requestWhenInUseAuthorization()
                selectedResult = MKMapItem(placemark: MKPlacemark(coordinate: destinationCoordinates))
            }
        }
        
        // --- here
        func getDirections2() {
            route = nil
            // Create and configure the request
            let request = MKDirections.Request()
            request.source = MKMapItem(placemark: MKPlacemark(coordinate: startingPoint))
            request.destination = MKMapItem(placemark: MKPlacemark(coordinate: destinationCoordinates))
            // Get the directions based on the request
            Task {
                let directions = MKDirections(request: request)
                let response = try? await directions.calculate()
                route = response?.routes.first
            }
        }
        
    }
    
    extension CLLocation {
        func isClose(to otherLocation: CLLocation, withinDistance distance: CLLocationDistance) -> Bool {
            return self.distance(from: otherLocation) <= distance
        }
    }
    

    Note, you also need to enable the appropriate privacy settings/info.plist of your App for location usage.