getting ETA ios - completion handler - swift - ios

I am new to IOS coding and using swift.
I am trying to calculate an ETA between 2 different points. when i convert the address to coordinates, i am storing those in a global var. problem is, since the value is set in a completion handler, when i make the call to calculate the ETA, the vars are not yet set. what would be another way to obtain the same result.
sourceCoords: CLLocationCoordinate2D?
destCoords: CLLocationCoordinate2D?
func getETA()
{
var locationManager:CLLocationManager = CLLocationManager();
locationManager.requestAlwaysAuthorization();
var authorizationStatus:CLAuthorizationStatus = CLLocationManager.authorizationStatus();
if(authorizationStatus == CLAuthorizationStatus.Authorized ||
authorizationStatus == CLAuthorizationStatus.AuthorizedWhenInUse)
{
let geoCoder = CLGeocoder()
let sourceAddress =
[kABPersonAddressStreetKey as NSString: "1 Stockton St",
kABPersonAddressCityKey: "San Francisco",
kABPersonAddressStateKey: "California",
kABPersonAddressZIPKey: "94108"]
//get coordinates
geoCoder.geocodeAddressDictionary(sourceAddress, completionHandler:
{
(placemarks: [AnyObject]!, error: NSError!) in
if error != nil {
println("Geocode failed with error: \(error.localizedDescription)")
} else if placemarks.count > 0 {
let placemark = placemarks[0] as CLPlacemark
let location = placemark.location
self.sourceCoords = location.coordinate
}
});
//var requestSource = MKMapItem.mapItemForCurrentLocation();
let addressDict =
[kABPersonAddressStreetKey as NSString: "2125 Chestnut St",
kABPersonAddressCityKey: "San Francisco",
kABPersonAddressStateKey: "California",
kABPersonAddressZIPKey: "94123"]
//get coordinates
geoCoder.geocodeAddressDictionary(addressDict, completionHandler:
{
(placemarks: [AnyObject]!, error: NSError!) in
if error != nil {
println("Geocode failed with error: \(error.localizedDescription)")
} else if placemarks.count > 0 {
let placemark = placemarks[0] as CLPlacemark
let location = placemark.location
self.destCoords = location.coordinate
}
});
let placeSource = MKPlacemark(coordinate: sourceCoords!,
addressDictionary: sourceAddress)
var requestSource = MKMapItem(placemark: placeSource);
println(placeSource.location.coordinate.latitude)
let place = MKPlacemark(coordinate: destCoords!,
addressDictionary: addressDict)
var requestDestination = MKMapItem(placemark: place);
println(place.location.coordinate.latitude)
var request:MKDirectionsRequest = MKDirectionsRequest();
request.setSource(requestSource);
request.setDestination(requestDestination);
request.transportType = MKDirectionsTransportType.Automobile;
request.requestsAlternateRoutes = false;
var directions:MKDirections = MKDirections(request: request)
directions.calculateDirectionsWithCompletionHandler({
(response: MKDirectionsResponse!, error: NSError?) in
if error != nil{
println("Error")
}
if response != nil{
var mkRoute:MKRoute = response.routes.last as MKRoute;
println(mkRoute.expectedTravelTime)
println(mkRoute.distance)
for step in mkRoute.steps
{
println(step.instructions);
}
}
else{
println("No response")
}
println(error?.description)
})
}

Related

Domain=kCLErrorDomain Code=8 when fetching location through zipcode

I'm trying to fetch the the latitude and longitude based on the input parameters postal/city and country code. Below is my code, this works fine if enter City and country name but shows error if I enter zipcode and country code. Below is the code. (Note: Location services and app permissions are enabled)
func getLocationFrom(postalCityCode: String, countryCode: String) -> CLLocation? {
let geocoder = CLGeocoder()
var location: CLLocation?
let address = CNMutablePostalAddress()
address.postalCode = postalCityCode
address.country = countryCode
geocoder.geocodePostalAddress(address, preferredLocale: Locale.current) { (placemarks, error) in
guard error == nil else {
print("Error: \(error!)")
return
}
guard let placemark = placemarks?.first else {
print("Error: placemark is nil")
return
}
guard let coordinate = placemark.location?.coordinate else {
print("Error: coordinate is nil")
return
}
location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
print("Found location = \(location)")
}
return location
}
Working input: Shanghai, CN
Failing input: 200040, CN
Edit
Attached updated code as suggested in the answer but still experiencing same issue
Currently, you are using return location before it is set, since geocoder.geocodePostalAddress(...) is an asynchronous function.
That means you need to use a completion handler (for example) to return the location, when it has the results, something like this:
func getLocationFrom(postalCityCode: String, countryCode: String, completion: #escaping ( CLLocation?) -> Void) {
let geocoder = CLGeocoder()
var location: CLLocation?
let address = CNMutablePostalAddress()
address.postalCode = postalCityCode
address.country = countryCode
geocoder.geocodePostalAddress(address, preferredLocale: Locale.current) { (placemarks, error) in
guard error == nil else {
print("Error: \(error!)")
return completion(nil)
}
guard let placemark = placemarks?.first else {
print("Error: placemark is nil")
return completion(nil)
}
guard let coordinate = placemark.location?.coordinate else {
print("Error: coordinate is nil")
return completion(nil)
}
location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
print("\n--> Found location = \(location) \n")
completion(location) // <-- here
}
}
Use it like this:
getLocationFrom(postalCityCode: "200040", countryCode: "CN") { location in
print("\n---> location: \(location) \n")
}
EDIT-1
for testing and isolating the issue, try this code in a new SwiftUI project:
struct ContentView: View {
#State var cityLocation = CLLocation()
var body: some View {
Text(cityLocation.description)
.onAppear {
getLocationFrom(postalCityCode: "200040", countryCode: "CN") { location in
print("\n---> location: \(location) \n")
if let theLocation = location {
cityLocation = theLocation
}
}
}
}
func getLocationFrom(postalCityCode: String, countryCode: String, completion: #escaping ( CLLocation?) -> Void) {
let geocoder = CLGeocoder()
var location: CLLocation?
let address = CNMutablePostalAddress()
address.postalCode = postalCityCode
address.country = countryCode
geocoder.geocodePostalAddress(address, preferredLocale: Locale.current) { (placemarks, error) in
guard error == nil else {
print("Error: \(error!)")
return completion(nil)
}
guard let placemark = placemarks?.first else {
print("Error: placemark is nil")
return completion(nil)
}
guard let coordinate = placemark.location?.coordinate else {
print("Error: coordinate is nil")
return completion(nil)
}
location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
print("\n--> Found location = \(location) \n")
completion(location)
}
}
}

CLPlacemark has no member 'get'

I am referring one func to get user's location and the code is below:
func updateLocation(_ userLocation: CLLocation) {
if (userLocation.horizontalAccuracy > 0) {
self.locationService.stopUpdatingLocation()
return
}
self.latitude = NSNumber(value: userLocation.coordinate.latitude as Double)
self.longitude = NSNumber(value: userLocation.coordinate.longitude as Double)
if !self.geocoder.isGeocoding {
self.geocoder.reverseGeocodeLocation(userLocation, completionHandler: {(placemarks, error) in
if let error = error {
logger.error("reverse geodcode fail: \(error.localizedDescription)")
return
}
if let placemarks = placemarks, placemarks.count > 0 {
// TODO:
let onePlacemark = placemarks.get(index: 0)
self.address = "\(onePlacemark?.administrativeArea,onePlacemark?.subLocality,onePlacemark?.thoroughfare)"
self.city = (onePlacemark?.administrativeArea!)!
self.street = (onePlacemark?.thoroughfare!)!
}
})
}
}
When building the project, it threw something "CLPlacemark has no member 'get'" at this line:
let onePlacemark = placemarks.get(index: 0)
I am writing the project with swift 4.0, and
import Foundation
import INTULocationManager
is done.

CLGeocoder reverseGeocodeLocation Energy leak issues

I am using a CLGeocoder().reverseGeocodeLocation and when it is ran, I get "very high" energy consumption and a bit of Overhead. Here's my code:
if CLLocationManager.authorizationStatus() == .AuthorizedWhenInUse {
var currentLatCoord = Double()
if manager.location?.coordinate.latitude != nil {
currentLatCoord = (manager.location?.coordinate.latitude)!
} else {
currentLatCoord = 0.0
print("oops!")
}
var currentLongCoord = Double()
if manager.location?.coordinate.longitude != nil {
currentLongCoord = (manager.location?.coordinate.longitude)!
} else {
currentLongCoord = 0.0
print("oops!")
}
CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: currentLatCoord, longitude: currentLongCoord)) { (placemarks, error) -> Void in
if error != nil {
print("oops!")
print(error)
return
}
let placeArray = placemarks as [CLPlacemark]!
var placeMark: CLPlacemark
placeMark = placeArray![0]
if let thoroughfare = placeMark.addressDictionary?["Thoroughfare"] as? String {
self.locationLabel.text = thoroughfare
} else {
self.locationLabel.text = "Error!"
print("oops!")
}
}
}
Here's the debugger:
If anyone could find out why, and I searched all over for the reasoning behind this and couldn't find it, please let me know!

Display route on map in Swift

I am trying to draw the route between two points on Apple map (Swift code).
The following structure is used to store the coordinates
struct GeoLocation {
var latitude: Double
var longitude: Double
func distanceBetween(other: GeoLocation) -> Double {
let locationA = CLLocation(latitude: self.latitude, longitude: self.longitude)
let locationB = CLLocation(latitude: other.latitude, longitude: other.longitude)
return locationA.distanceFromLocation(locationB)
}
}
self.foundLocations - is an array of these structures
In the custom class I recieve the coordinates of the points on the map.
var coordinates = self.foundLocations.map{$0.coordinate}
Then I draw the route on the map
self.polyline = MKPolyline(coordinates: &coordinates, count: coordinates.count)
self.mapView.addOverlay(self.polyline, level: MKOverlayLevel.AboveRoads)
To draw the route I use the following method from MKMapViewDelegate
func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
if let polylineOverlay = overlay as? MKPolyline {
let render = MKPolylineRenderer(polyline: polylineOverlay)
render.strokeColor = UIColor.blueColor()
return render
}
return nil
}
Instead of the actual route laying on roads I get just a straight line between two points.
How can I display the actual route?
You actually have to fetch the route from Apple's maps' server using calculateDirectionsWithCompletionHandler.
First create the relevant MKMapItems for both the source and destination, ex:
let geocoder = CLGeocoder()
let location = CLLocation(latitude: sourceLatitude, longitude: sourceLongitude)
geocoder.reverseGeocodeLocation(location, completionHandler: {
(placemarks:[AnyObject]?, error:NSError?) -> Void in
if placemarks?.count > 0 {
if let placemark: MKPlacemark = placemarks![0] as? MKPlacemark {
self.source = MKMapItem(placemark: placemark)
}
}
})
(Repeat for destination.)
Then fetch the MKRoute, ex:
let request:MKDirectionsRequest = MKDirectionsRequest()
// source and destination are the relevant MKMapItems
request.setSource(source)
request.setDestination(destination)
// Specify the transportation type
request.transportType = MKDirectionsTransportType.Automobile;
// If you're open to getting more than one route,
// requestsAlternateRoutes = true; else requestsAlternateRoutes = false;
request.requestsAlternateRoutes = true
let directions = MKDirections(request: request)
directions.calculateDirectionsWithCompletionHandler ({
(response: MKDirectionsResponse?, error: NSError?) in
if error == nil {
self.directionsResponse = response
// Get whichever currentRoute you'd like, ex. 0
self.route = directionsResponse.routes[currentRoute] as MKRoute
}
})
Then after retrieving the MKRoute, you can add the polyline to the map like so:
mapView.addOverlay(route.polyline, level: MKOverlayLevel.AboveRoads)
Swift 3 and reusable conversion of Lyndsey Scott's answer:
final class Route {
static func getRouteFor(
source: CLLocationCoordinate2D,
destination: CLLocationCoordinate2D,
completion: #escaping (
_ route: MKRoute?,
_ error: String?)->()
) {
let sourceLocation = CLLocation(
latitude: source.latitude,
longitude: source.longitude
)
let destinationLocation = CLLocation(
latitude: destination.latitude,
longitude: destination.longitude
)
let request = MKDirectionsRequest()
self.getMapItemFor(location: sourceLocation) { sourceItem, error in
if let e = error {
completion(nil, e)
}
if let s = sourceItem {
self.getMapItemFor(location: destinationLocation) { destinationItem, error in
if let e = error {
completion(nil, e)
}
if let d = destinationItem {
request.source = s
request.destination = d
request.transportType = .walking
let directions = MKDirections(request: request)
directions.calculate(completionHandler: { response, error in
if let r = response {
let route = r.routes[0]
completion(route, nil)
}
})
}
}
}
}
}
static func getMapItemFor(
location: CLLocation,
completion: #escaping (
_ placemark: MKMapItem?,
_ error: String?)->()
) {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { placemark, error in
if let e = error {
completion(nil, e.localizedDescription)
}
if let p = placemark {
if p.count < 1 {
completion(nil, "placemark count = 0")
} else {
if let mark = p[0] as? MKPlacemark {
completion(MKMapItem(placemark: mark), nil)
}
}
}
}
}
}
Usage:
Route.getRouteFor(source: CLLocationCoordinate2D, destination: CLLocationCoordinate2D) { (MKRoute?, String?) in
<#code#>
}

iOS Swift coordinates function returns nil

I'm working on a function to convert a city (string) to coordinates. However, when I call the function I get "(0.0, 0.0)" as a result. It should be the latitude and longitude.
Please help me out. Thanks!
This is the function
func getCoordinates(huidigeLocatie: String) -> (lat: CLLocationDegrees, long: CLLocationDegrees) {
var lat:CLLocationDegrees
var long:CLLocationDegrees
var geocoderHuidigeLocatie = CLGeocoder()
geocoderHuidigeLocatie.geocodeAddressString(huidigeLocatie, completionHandler:
{(placemarks: [AnyObject]!, error: NSError!) in
if error != nil {
println("Geocode failed with error: \(error.localizedDescription)")
} else if placemarks.count > 0 {
let placemark = placemarks[0] as CLPlacemark
let location = placemark.location
var lat = location.coordinate.latitude
var long = location.coordinate.longitude
}
})
return (lat: CLLocationDegrees(), long: CLLocationDegrees())
}
There are two issues here:
You want to return the actual lat and long variables, not CLLocationDegrees().
A more subtle issue is that you're calling a function that returns its results asynchronously, so you cannot return the values immediately. Instead, you might employ your own completionHandler pattern.
For example:
func getCoordinates(huidigeLocatie: String, completionHandler: (lat: CLLocationDegrees!, long: CLLocationDegrees!, error: NSError?) -> ()) -> Void {
var lat:CLLocationDegrees
var long:CLLocationDegrees
var geocoderHuidigeLocatie = CLGeocoder()
geocoderHuidigeLocatie.geocodeAddressString(huidigeLocatie) { (placemarks: [AnyObject]!, error: NSError!) in
if error != nil {
println("Geocode failed with error: \(error.localizedDescription)")
completionHandler(lat: nil, long: nil, error: error)
} else if placemarks.count > 0 {
let placemark = placemarks[0] as CLPlacemark
let location = placemark.location
let lat = location.coordinate.latitude
let long = location.coordinate.longitude
completionHandler(lat: lat, long: long, error: nil)
}
}
}
And you'd call it like so:
getCoordinates(string) { lat, long, error in
if error != nil {
// handle the error here
} else {
// use lat, long here
}
}
// but not here
You should return (lat: lat, long: long).

Resources