Swift - CLGeocoder reverseGeocodeLocation completionHandler closure

AaronDancer picture AaronDancer · Jun 21, 2014 · Viewed 28.2k times · Source

What I'm trying to do is pass a CLLocation to the function getPlacemarkFromLocation which then uses the passed CLLocation through reverseGeocodeLocation to set the CLPlacemark? that will be returned.

I'm having issues creating the completionHandler closure in reverseGeocodeLocation, it's throwing a compiler error/crash:


In Swift, CLGeocodeCompletionHandler is CLGeocodeCompletionHandler = (AnyObject[]!, NSError!) -> Void according to the documentation AnyObject[]! is supposed to contain CLPlacemark objects just like the Objective-C version.

Here's my current code:

class func getPlacemarkFromLocation(location:CLLocation)->CLPlacemark?{
    var g = CLGeocoder()
    var p:CLPlacemark?
    g.reverseGeocodeLocation(location, completionHandler: {
        (placemarks, error) in
        let pm = placemarks as? CLPlacemark[]
        if (pm && pm?.count > 0){
            p = placemarks[0] as? CLPlacemark
        }
    })
    return p?
}

EDIT: It seems like the error had to do with placemarks.count with placemarks not being treated like an array. It compiles now, however I'm getting nothing but nil when trying to set p inside the completionHandler. I've checked the CLLocations being passed and they are valid.

EDIT 2: After printing placemarks, I can confirm that it returns data. However p is still returning nil.

Answer

AaronDancer picture AaronDancer · Jun 24, 2014

I found the answer I needed in this thread: Set address string with reverseGeocodeLocation: and return from method

The issue lies with the fact that reverseGeocodeLocation is asynchronous, the method is returning a value before the completionBlock sets p in my example.


As requested, here's my current code.

func showAddViewController(placemark:CLPlacemark){
    self.performSegueWithIdentifier("add", sender: placemark) 
}

func getPlacemarkFromLocation(location: CLLocation){
    CLGeocoder().reverseGeocodeLocation(location, completionHandler:
        {(placemarks, error) in
            if error {println("reverse geodcode fail: \(error.localizedDescription)")}
            let pm = placemarks as [CLPlacemark]
            if pm.count > 0 { self.showAddPinViewController(placemarks[0] as CLPlacemark) }
    })
}

I didn't want to take the NSNotificationCenter route because that would add unnecessary overhead, rather inside the completionHandler closure I call upon another function and pass the CLPlacemark generated by getPlacemarkFromLocation as a parameter to keep things asynchronous since the function will be called after placemarks is set the function (should) receive the placemark needed and execute the code you want. Hope what I said makes sense.