Reputation: 333
I made a class function which contains a closure that reverse geocodes location coordinates into a address string. I initially had it written in a viewcontroller, but I need to run this code in a few spots.
My problem is that the class function returns the string before the closure runs, so the returning string is blank. What are ways I can fix this? And are there better ways to organize my code rather than using a class function?
import CoreLocation
class LocationUtil: CLLocation {
class func getLocationAddress(location:CLLocation?) -> NSString {
var geocoder = CLGeocoder()
var addressString : String = ""
if location != nil {
geocoder.reverseGeocodeLocation(location, completionHandler: {(placemarks, error)->Void in
var placemark:CLPlacemark!
if error == nil && placemarks.count > 0 {
placemark = placemarks[0] as CLPlacemark
if placemark.subThoroughfare != nil {
addressString = placemark.subThoroughfare + " "
}
if placemark.thoroughfare != nil {
addressString = addressString + placemark.thoroughfare + ", "
}
if placemark.locality != nil {
addressString = addressString + placemark.locality + ", "
}
if placemark.administrativeArea != nil {
addressString = addressString + placemark.administrativeArea + " "
}
if placemark.postalCode != nil {
addressString = addressString + placemark.postalCode + " "
}
if placemark.country != nil {
addressString = addressString + placemark.country
}
}
println("Address in closure: \(addressString)") //prints addresss
})
}
println("Address out of closure: \(addressString)") // doesn't print address
return addressString //this returns nothing
}
}
Upvotes: 3
Views: 2868
Reputation: 10475
The geocoder.reverseGeocodeLocation
runs asynchronously and calls the completionHandler
only after it completes.
So, getting a return value is obviously not an option. Instead, accept closure as an input/argument for your class function
class func getLocationAddress(location: CLLocation, getLocCompletionHandler : (addressString : String?, error : NSError?) -> Void) {
geocoder.reverseGeocodeLocation(location, completionHandler: {(placemarks, error)->Void in
//Your current code
//...
//...
println("Address: \(addressString)")
//Instead of returning the address string, call the 'getLocCompletionHandler'
getLocCompletionHandler(addressString: addressString, error: error)
})
}
And when you call this function, instead of working with the return value, wait for the closure to be called:
LocationUtil.getLocationAddress(currentLocation, completionHandler: { (addressObject, error) -> Void in
println("Address: \(addressString)")
//OR handle the error appropriately
}
Read up on closures to get a better understanding of the concept.
As far as organisation of code goes, class function is a good choice in this case.
Upvotes: 9
Reputation: 5083
You should return the completionHandler
inside your
geocoder.reverseGeocodeLocation(location, completionHandler: {(placemarks, error)->Void in
The completionHandler
will be called when the function is finished. For this to work you should also make your own function return asynchronously.
This is needed because your reverseGeocodeLocation
function is ran asynchronously. Your main thread will continue to run your getLocationAddress
so it will return the string but it is empty because it is returned before the asynchronous function finished.
Upvotes: 4