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

ぃ、小莉子 提交于 2020-03-23 12:03:36

问题


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.


回答1:


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



回答2:


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)
}



回答3:


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)
        })
    }


来源:https://stackoverflow.com/questions/52059914/cannot-wait-for-the-result-of-mkdirections-calculate-getting-nil-instead-of-it

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!