Viktor Csimma
Viktor Csimma

Reputation: 33

Cannot wait for the result of MKDirections.calculate, getting nil instead of it

I would like to calculate the real walking distance between my current location and a list of CLLocations using MKDirections.calculate. However, for some reason the return command at the end of the function does not wait for the result and tries to return the empty variable. My code looks like this:

func getDistance (location1: CLLocation, location2: CLLocation) {
    let coordinates1 = location1.coordinate
    let placemark1 = MKPlacemark(coordinate: coordinates1)
    let sourceItem = MKMapItem(placemark: placemark1)
    let coordinates2 = location2.coordinate
    let placemark2 = MKPlacemark(coordinate: coordinates2)
    let destinationItem = MKMapItem(placemark: placemark2)

    let request = MKDirectionsRequest()
    request.source = sourceItem
    request.destination = destinationItem
    request.requestsAlternateRoutes = true
    request.transportType = .walking

    var distance: Double?

    let directions = MKDirections(request: request)
    directions.calculate { (response, error) in

        if var routeResponse = response?.routes {
            routeResponse.sort(by: {$0.expectedTravelTime < $1.expectedTravelTime})
            let quickestRoute: MKRoute = routeResponse[0]
            distance = Double(quickestRoute.distance)
        }
    }

    return distance //returns nil
}

And after that I would like to use the function in a code like this:

let myLocation = CLLocation(latitude: 47.0, longitude: 17.0)
let destinationArray = [CLLocation(latitude: 47.1, longitude: 17.1), CLLocation(latitude: 47.2, longitude: 17.2), CLLocation(latitude: 47.3, longitude: 17.3)]
var distanceArray: [Double] = []
for destination in destinationArray {
    distanceArray.append(getDistance(location1: myLocation, location2: destination))
}
return distanceArray

I have tried closures, but they did not work because I could not find a way to return distanceArray (the same error, it did not wait for the closure to run and returned the empty array). I have also tried DispatchGroups but they had no effect (maybe I implemented them in the wrong way).

I would really appreciate your help.

Thank you.

Upvotes: 2

Views: 1013

Answers (3)

Mehul
Mehul

Reputation: 3178

Using MapKit & Swift 5

Calculate distance between two location location, It will help anyone

Sample Function : I have tested in Google Map as well as Apple Map

        let startLocation : CLLocation = CLLocation.init(latitude: 23.0952779, longitude: 72.5274129)
        let endLocation : CLLocation = CLLocation.init(latitude: 23.0981711, longitude: 72.5294229)
        let distance = startLocation.distance(from: endLocation)
        self.getDistance(departureDate: Date().adjust(hour: 8, minute: 0, second: 0, day: 0, month: 0), arrivalDate: Date().adjust(hour: 8, minute: 10, second: 0, day: 0, month: 0), startLocation: startLocation, endLocation: endLocation) { (distanceInMeters) in

            print("fake distance: \(distance)")
            let fakedistanceInMeter = Measurement(value: distance, unit: UnitLength.meters)
            let fakedistanceInKM = fakedistanceInMeter.converted(to: UnitLength.kilometers).value
            let fakedistanceInMiles = fakedistanceInMeter.converted(to: UnitLength.miles).value
            print("fakedistanceInKM :\(fakedistanceInKM)")
            print("fakedistanceInMiles :\(fakedistanceInMiles)")


            print("actualDistance : \(distanceInMeters)")

            let distanceInMeter = Measurement(value: distanceInMeters, unit: UnitLength.meters)
            let distanceInKM = distanceInMeter.converted(to: UnitLength.kilometers).value
            let distanceInMiles = distanceInMeter.converted(to: UnitLength.miles).value
            print("distanceInKM :\(distanceInKM)")
            print("distanceInMiles :\(distanceInMiles)")
        }

Use of functions

                    self.getDistance(departureDate: trip.departure.dateTime, arrivalDate: trip.arrival.dateTime, startLocation: startLocation, endLocation: endLocation) { (actualDistance) in
                        print("actualDistance : \(actualDistance)")
                    }

I am improved above function and added code here, I hope it will help someone.

func calculateDistancefrom(departureDate: Date, arrivalDate: Date, sourceLocation: MKMapItem, destinationLocation: MKMapItem, doneSearching: @escaping (_ distance: CLLocationDistance) -> Void) {

        let request: MKDirections.Request = MKDirections.Request()

        request.departureDate = departureDate
        request.arrivalDate = arrivalDate

        request.source = sourceLocation
        request.destination = destinationLocation

        request.requestsAlternateRoutes = true
        request.transportType = .automobile

        let directions = MKDirections(request: request)
        directions.calculate { (directions, error) in
            if var routeResponse = directions?.routes {
                routeResponse.sort(by: {$0.expectedTravelTime <
                    $1.expectedTravelTime})
                let quickestRouteForSegment: MKRoute = routeResponse[0]

                doneSearching(quickestRouteForSegment.distance)
            }
        }
    }

    func getDistance(departureDate: Date, arrivalDate: Date, startLocation : CLLocation, endLocation : CLLocation, completionHandler: @escaping (_ distance: CLLocationDistance) -> Void) {

        let destinationItem =  MKMapItem(placemark: MKPlacemark(coordinate: startLocation.coordinate))
        let sourceItem      =  MKMapItem(placemark: MKPlacemark(coordinate: endLocation.coordinate))
        self.calculateDistancefrom(departureDate: departureDate, arrivalDate: arrivalDate, sourceLocation: sourceItem, destinationLocation: destinationItem, doneSearching: { distance in
            completionHandler(distance)
        })
    }

Upvotes: 1

Ashley Mills
Ashley Mills

Reputation: 53082

directions.calculate is an asynchronous function, so you need to wait for the function to return before returning the calculated distance. You should do this using a completion handler…

func getDistance(location1: CLLocation, location2: CLLocation, completion: @escaping (Double) -> Void) {

    // etc 
    directions.calculate { (response, error) in

        if var routeResponse = response?.routes {
            routeResponse.sort(by: {$0.expectedTravelTime < $1.expectedTravelTime})
            let quickestRoute: MKRoute = routeResponse[0]

            completion(Double(quickestRoute.distance))
        }
    }
}

and then you should do something similar with your distance array. You'll need to wait for all getDistance calls to return, so you can use a DispatchGroup for that…

func getDistanceArray(completion: @escaping ([Double]) -> Void) {

    let group = DispatchGroup()
    var distanceArray: [Double] = []

    for destination in destinationArray {
        group.enter()
        getDistance(location1: myLocation, location2: destination) { distance in
            distanceArray.append(distance)
            group.leave()
        }
    }
    group.wait()
    completion(distanceArray)
}

which you can call…

getDistanceArray { distanceArray in
    print(distanceArray)
}

Upvotes: 0

Kosuke Ogawa
Kosuke Ogawa

Reputation: 7451

Try the following closure:

func getDistance (location1: CLLocation, location2: CLLocation,
    completion: @escaping(Double?) -> Void) {

    let coordinates1 = location1.coordinate
    let placemark1 = MKPlacemark(coordinate: coordinates1)
    let sourceItem = MKMapItem(placemark: placemark1)
    let coordinates2 = location2.coordinate
    let placemark2 = MKPlacemark(coordinate: coordinates2)
    let destinationItem = MKMapItem(placemark: placemark2)

    let request = MKDirectionsRequest()
    request.source = sourceItem
    request.destination = destinationItem
    request.requestsAlternateRoutes = true
    request.transportType = .walking

    var distance: Double?

    let directions = MKDirections(request: request)
    directions.calculate { (response, error) in

        if var routeResponse = response?.routes {
            routeResponse.sort(by: {$0.expectedTravelTime < $1.expectedTravelTime})
            let quickestRoute: MKRoute = routeResponse[0]
            distance = Double(quickestRoute.distance)
            completion(distance)
        }
    }
}

Usage:

    let myLocation = CLLocation(latitude: 47.0, longitude: 17.0)
    let destinationArray = [CLLocation(latitude: 47.1, longitude: 17.1), CLLocation(latitude: 47.2, longitude: 17.2), CLLocation(latitude: 47.3, longitude: 17.3)]
    var distanceArray: [Double] = []
    for destination in destinationArray {
        getDistance(location1: myLocation, location2: destination) { distance in
            print("distance", distance)
            if let distance = distance {
                distanceArray.append(distance)
            }
        }
    }
    return distanceArray

Upvotes: 0

Related Questions