Why .geocodeAddressString doesn't change external variable? - ios

I have an external variable chooseCoordinates, which has a type of CLLocationCoordinate2D. This var need to save coordinates from geocodeAddressString closure, but, apparently, it doesn't change.
I would like to ask for advice, how to make this actually closure store the data, so I will be able to parse it to another viewController
var chooseCoordinates = CLLocationCoordinate2D()
////////////
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(sender.text!, completionHandler: { (placemarks, error) -> Void in
if(error != nil) {
print("\(error)")
}
if let placemark = placemarks?.first {
let coordinates: CLLocationCoordinate2D = placemark.location!.coordinate
self.chooseCoordinates = coordinates
}
})

The geocodeAddressString runs asynchronously (i.e. even though the method returns immediately, its completionHandler closure may be called later). So, are you sure it's not changing, or just not changing by the time you try to use chooseCoordinates? You should initiate whatever updating of the UI or whatever from within the closure (or the didSet of chooseCoordinates), not doing so immediately. We don't see how you're using chooseCoordinates, so it's hard to be more specific than that.
For example:
geocoder.geocodeAddressString(sender.text!) { placemarks, error in
if error != nil {
print(error!)
}
if let placemark = placemarks?.first {
let coordinates: CLLocationCoordinate2D = placemark.location!.coordinate
self.chooseCoordinates = coordinates
// call the method that uses `chooseCoordinates` here
}
}
// don't try to use `chooseCoordinates` here, as it hasn't been set yet.
Or, you might employ the completionHandler pattern yourself:
#IBAction func didEndEditingTextField(sender: UITextField) {
geocodeAddressString(sender.text!) { coordinate, error in
self.chooseCoordinates = coordinate
// trigger whatever the next step is here, or in the `didSet` of `chooseCoordinates`
}
// don't try to use `chooseCooordinates` here
}
var chooseCoordinates: CLLocationCoordinate2D? // you probably should make this optional
let geocoder = CLGeocoder()
/// Geocode string
///
/// - parameter string: The string to geocode.
/// - parameter completionHandler: The closure that is called asynchronously (i.e. later) when the geocoding is done.
func geocodeAddressString(string: String, completionHandler: (CLLocationCoordinate2D?, NSError?) -> ()) {
geocoder.geocodeAddressString(string) { placemarks, error in
if error != nil {
print(error!)
}
if let placemark = placemarks?.first {
completionHandler(placemark.location!.coordinate, error)
} else {
completionHandler(nil, error)
}
}
}

Related

How to return a value from CLGeocoder?

I'm working on the weather app as a training and there's a need to convert location to city. So I'm using CLGeocoder like so:
func updateWeatherData(json: JSON?) {
if let json = json {
weatherData.temperature = fahrenheitToCelcius(json["currently"]["temperature"].doubleValue)
weatherData.weatherIconName = json["currently"]["icon"].stringValue
let location = CLLocation(latitude: json["latitude"].doubleValue, longitude: json["longitude"].doubleValue)
CLGeocoder().reverseGeocodeLocation(location) { (placemark, error) in
if let city = placemark {
self.weatherData.city = city.last?.locality
} else if let error = error {
print(error)
}
}
updateUIWithWeatherData()
}
}
//MARK: - UI Updates
func updateUIWithWeatherData() {
cityLabel.text = weatherData.city
temperatureLabel.text = "\(weatherData.temperature)°"
weatherIcon.image = UIImage(named: weatherData.weatherIconName!)
}
And this code returns nil to weatherData.city. But when I place a breakpoint inside a closure, everything works fine. What am I missing?
If I understand your issue correctly you need to update your UI after the geocoding is complete. Like the following:
func updateWeatherData(json: JSON?) {
if let json = json {
weatherData.temperature = fahrenheitToCelcius(json["currently"]["temperature"].doubleValue)
weatherData.weatherIconName = json["currently"]["icon"].stringValue
let location = CLLocation(latitude: json["latitude"].doubleValue, longitude: json["longitude"].doubleValue)
CLGeocoder().reverseGeocodeLocation(location) { (placemark, error) in
if let city = placemark {
self.weatherData.city = city.last?.locality
} else if let error = error {
print(error)
}
self.updateUIWithWeatherData()
}
}
}
The order of these operations is so that the geocoding is done asynchronously and may occur later then the code called after it. Note may but does not need to.
You should also read documentation of this method about threading. UI must be updated on main thread so unless the documentation specifies that the call will be done on main thread you are best forcing it:
CLGeocoder().reverseGeocodeLocation(location) { (placemark, error) in
if let city = placemark {
self.weatherData.city = city.last?.locality
} else if let error = error {
print(error)
}
DispatchQueue.main.async {
self.updateUIWithWeatherData()
}
}

Passing in values to a closure function

Check out this code:
func getReversedGeocodeLocation(location: CLLocation, completionHandler: #escaping ()->()) {
CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
if error != nil {
print("Reverse geocoder failed with error" + error!.localizedDescription)
return
}
if placemarks != nil {
if placemarks!.count > 0 {
let pm = placemarks![0]
if let addressDictionary: [AnyHashable: Any] = pm.addressDictionary,
let addressDictionaryFormatted = addressDictionary["FormattedAddressLines"] {
let address = (addressDictionaryFormatted as AnyObject).componentsJoined(by: ", ")
self.addressInViewController = address
}
completionHandler()
}
} else {
print("Problem with the data received from geocoder")
}
})
}
In the viewController
override func viewDidLoad() {
var addressInViewController = String()
getReversedGeocodeLocation(location: location, completionHandler: {
print("After geo finished")
})
}
This is a simple case for using closures. As you can see, when the reverse geo finishes, it updates the addressInViewController variable which is defined outside the function itself. I'm a bit confused when it comes to closures but I do know it's essentially passing in another function as a parameter, into a function. So can I pass in something like (_ String: x)->() instead of ()->() where the address variable would be populated from the main reverse geo function and passed along? I tried doing that but it says "x" is undefined. If this is achieve-able then I guess I can decouple my code in a better way using closures.
Thanks and have a great day :)
define your methods like this
func getReversedGeocodeLocation(location: CLLocation, completionHandler: #escaping (_ value : Any)->()) {
CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
if error != nil {
print("Reverse geocoder failed with error" + error!.localizedDescription)
return
}
if placemarks != nil {
if placemarks!.count > 0 {
let pm = placemarks![0]
if let addressDictionary: [AnyHashable: Any] = pm.addressDictionary,
let addressDictionaryFormatted = addressDictionary["FormattedAddressLines"] {
let address = (addressDictionaryFormatted as AnyObject).componentsJoined(by: ", ")
self.addressInViewController = address
}
completionHandler(address)
}
} else {
print("Problem with the data received from geocoder")
}
})
}
override func viewDidLoad() {
var addressInViewController = String()
getReversedGeocodeLocation(location: location, completionHandler: { (_ values : Any) in
self. addressInViewController = values
})
}
Make dataType of Value according to your need.

GCD Semaphore does not wait (Swift)

I'm pretty much new to GCD. I have this function for forward geocoding and the issue is it returns before the completion closure completes. So every time it just returns nil. I found out I can use semaphores so the return waits for the completion closure to complete, but there are very little examples online and I found none of a function that returns. I tried to implement it, but function still returns nil even though the location is printed out to the console moments later. If someone could tell me where I am making a mistake I would be very grateful.
func forwardGeocoding(address: String) -> CLLocation? {
var userLocation: CLLocation?
var returnvalue: CLLocation?
let semaphore = dispatch_semaphore_create(0)
CLGeocoder().geocodeAddressString(address, completionHandler: { (placemarks, error) in
if error != nil {
print("Geocoding error: \(error)")
return
}
if placemarks?.count > 0 {
let placemark = placemarks?.first
let location = placemark?.location
let coordinate = location?.coordinate
print("Settings location: \(coordinate!.latitude), \(coordinate!.longitude)")
if let unwrappedCoordinate = coordinate {
let CLReadyLocation: CLLocation = CLLocation(latitude: unwrappedCoordinate.latitude, longitude: unwrappedCoordinate.longitude)
userLocation = CLReadyLocation
dispatch_semaphore_signal(semaphore)
}
}
})
let wait = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
if wait != 0 {
returnvalue = userLocation
}
return returnvalue
}
As Paulw11 already mentioned, a semaphore in this case is very bad programming habit. If you are new to GCD learn to understand the asynchronous pattern for returning received data in a completion block. It's even simpler to handle in Swift than in Objective-C.
This is an example using a completion block:
func forwardGeocoding(address: String, completion: (CLLocation?, NSError?) -> Void) {
CLGeocoder().geocodeAddressString(address, completionHandler: { (placemarks, error) in
if error != nil {
completion(nil, error!)
} else {
if let placemarks = placemarks where !placemarks.isEmpty {
let placemark = placemarks.first!
if let unwrappedLocation = placemark.location {
let coordinate = unwrappedLocation.coordinate
print("Settings location: \(coordinate.latitude), \(coordinate.longitude)")
let CLReadyLocation = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
completion(CLReadyLocation, nil)
}
}
}
})
}
and call it with
forwardGeocoding("foo") { (location, error) in
if error != nil {
print("Geocoding error: \(error!)")
} else {
// do something with the location
}
}
the result of dispatch_semaphore_wait is 0 on success and non zero on timeout occurred. So you should change your code to:
let wait = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
if wait == 0 { //your work is done, without time out.
returnvalue = userLocation //update location
}
return returnvalue //otherwise return nil according to your code above. this code will never execute. In this case, there is no time out cause it wait forever.
Another point, you must call dispatch_semaphore_signal before your block geocodeAddressString end the execution. Otherwise, your application will wait forever in case of getting error.
CLGeocoder().geocodeAddressString(address, completionHandler: { (placemarks, error) in
if error != nil {
print("Geocoding error: \(error)")
dispatch_semaphore_signal(semaphore) //add to here
return
}
if placemarks?.count > 0 {
let placemark = placemarks?.first
let location = placemark?.location
let coordinate = location?.coordinate
print("Settings location: \(coordinate!.latitude), \(coordinate!.longitude)")
if let unwrappedCoordinate = coordinate {
let CLReadyLocation: CLLocation = CLLocation(latitude: unwrappedCoordinate.latitude, longitude: unwrappedCoordinate.longitude)
userLocation = CLReadyLocation
}
}
dispatch_semaphore_signal(semaphore) //and here
})
Finally, when using semaphore that wait forever, you have to make sure that the block of code inside semaphore will end the execution.

Completion handler asynchrony

I have a problem with my completion handlers. Here is a function with a completion handler, located in a Utility file:
func convertGeopointToCity(geopoint: PFGeoPoint, complete: (city: String, error: NSError?) -> Void) {
var city = ""
let latitude = geopoint.latitude
let longitude = geopoint.longitude
let location: CLLocation = CLLocation(latitude: latitude, longitude: longitude)
CLGeocoder().reverseGeocodeLocation(location, completionHandler: { placemarks, error in
if (error == nil) {
if let p = CLPlacemark?(placemarks![0]) {
if let city = p.locality {
city = " \(city)!"
print("Here's the city:\(city)")
complete(city: city, error: nil)
}
}
}
})
}
That I call in a ViewController
LocationUtility.instance.convertGeopointToCity(geopoint, complete: { result, error in
if error != nil {
print("error converting geopoint")
} else {
city = result as String
}
})
print("The city: \(city)")
The output clearly indicates that the function is not waiting to be completed before running the block:
The city:
Here's the hood Toronto!
How do I address this issue?
You should put your handler inside of the block:
LocationUtility.instance.convertGeopointToCity(geopoint, complete: { result, error in
if error != nil {
print("error converting geopoint")
} else {
city = result as String
// do other stuff here, or call a method
print("The city: \(city)")
}
})
The CLGeocoder().reverseGeocodeLocation is async. The completion block is called as soon as the geocode finished, more likely after your print append.
You should do your operations in another function called by the complete block, or adding some code to the block itself.

Callbacks, completion handler with multiple parameters in Swift

I reverse geocode in a few places in my map and I wanted to wrap it to some common method. What I have is this as a starting point that works:
func reverseGeocodeLocation(location: CLLocation, completion: (CLPlacemark) -> Void) {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) {
(placemarks, error) -> Void in
// Check for returned placemarks
if let placemarks = placemarks where placemarks.count > 0 {
let topResult = placemarks[0] as! CLPlacemark
completion(topResult)
}
}
}
However, if I want to add another parameter to my completion method that would return the error to my view like so:
func reverseGeocodeLocation(location: CLLocation, completion: (CLPlacemark, NSError) -> Void) {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) {
(placemarks, error) -> Void in
// Check for returned placemarks
if let placemarks = placemarks where placemarks.count > 0 {
let topResult = placemarks[0] as! CLPlacemark
completion(topResult, error)
}
}
}
If error is nil, then my completion(topResult, error) will fail because error is nil with bad access. But I cannot do this since the parameters are not matching the completion handler.
if error != nil {
completion(topResult)
} else {
completion(...) // do something with the error
}
So if I have a nil error, how would I call my completion handler?
Since you own definition of your completion header your can define it normally with optional NSError? So it will not cause errors and will require unwrapping. Moreover recent definition of CLGeocodeCompletionHandler also uses optional array and error:
typealias CLGeocodeCompletionHandler = ([CLPlacemark]?, NSError?) -> Void

Resources