问题
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