Swift - CLGeocoder reverseGeocodeLocation completionHandler closure

29,409

Solution 1

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.

Solution 2

With these lines of Swift, you can print out fully the location's address:

func getLocationAddress(location:CLLocation) {
    var geocoder = CLGeocoder()

    println("-> Finding user address...")

    geocoder.reverseGeocodeLocation(location, completionHandler: {(placemarks, error)->Void in
        var placemark:CLPlacemark!

        if error == nil && placemarks.count > 0 {
            placemark = placemarks[0] as CLPlacemark

            var addressString : String = ""
            if placemark.ISOcountryCode == "TW" /*Address Format in Chinese*/ {
                if placemark.country != nil {
                    addressString = placemark.country
                }
                if placemark.subAdministrativeArea != nil {
                    addressString = addressString + placemark.subAdministrativeArea + ", "
                }
                if placemark.postalCode != nil {
                    addressString = addressString + placemark.postalCode + " "
                }
                if placemark.locality != nil {
                    addressString = addressString + placemark.locality
                }
                if placemark.thoroughfare != nil {
                    addressString = addressString + placemark.thoroughfare
                }
                if placemark.subThoroughfare != nil {
                    addressString = addressString + placemark.subThoroughfare
                }
            } else {
                if placemark.subThoroughfare != nil {
                    addressString = placemark.subThoroughfare + " "
                }
                if placemark.thoroughfare != nil {
                    addressString = addressString + placemark.thoroughfare + ", "
                }
                if placemark.postalCode != nil {
                    addressString = addressString + placemark.postalCode + " "
                }
                if placemark.locality != nil {
                    addressString = addressString + placemark.locality + ", "
                }
                if placemark.administrativeArea != nil {
                    addressString = addressString + placemark.administrativeArea + " "
                }
                if placemark.country != nil {
                    addressString = addressString + placemark.country
                }
            }

            println(addressString)
        }
    })
}

Cheers!

Solution 3

Here is closure that worked for me -- it took awhile to get it to work. I think your problem is related to not initializing p with the correct initializer. I tried a few variations until I got this to work: self.placemark = CLPlacemark(placemark: stuff[0] as CLPlacemark)

geocoder.reverseGeocodeLocation(newLocation, completionHandler: {(stuff, error)->Void in

        if error {
            println("reverse geodcode fail: \(error.localizedDescription)")
            return
        }

        if stuff.count > 0 {
            self.placemark = CLPlacemark(placemark: stuff[0] as CLPlacemark)

            self.addressLabel.text = String(format:"%@ %@\n%@ %@ %@\n%@",
                self.placemark.subThoroughfare ? self.placemark.subThoroughfare : "" ,
                self.placemark.thoroughfare ? self.placemark.thoroughfare : "",
                self.placemark.locality ? self.placemark.locality : "",
                self.placemark.postalCode ? self.placemark.postalCode : "",
                self.placemark.administrativeArea ? self.placemark.administrativeArea : "",
                self.placemark.country ? self.placemark.country : "")
        }
        else {
            println("No Placemarks!")
            return
        }

        })

EDIT:

moved better answer to its own answer.

Share:
29,409

Related videos on Youtube

AaronDancer
Author by

AaronDancer

Updated on February 27, 2020

Comments

  • AaronDancer
    AaronDancer about 4 years

    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.

  • AaronDancer
    AaronDancer almost 10 years
    It does seem like p not being initialized is the issue, so I've changed p = placemarks[0] as? CLPlacemark to p = CLPlacemark(placemark: placemarks[0] as? CLPlacemark) however p still returns nil. I tried initializing p before reverseGeocodeLocation is called, however I get EXC_BAD_ACCESS when trying to change the value. I cannot use the CLPlacemark(placemark: CLPlacemark?) initializer beforehand because I have no CLPlacemark to give
  • AaronDancer
    AaronDancer almost 10 years
    I've tried that as well however I get EXC_BAD_ACCESS when doing so.
  • AaronDancer
    AaronDancer almost 10 years
    I've tried it again, I'm getting the same result as your example above when using a non-optional value.
  • JohnInSea
    JohnInSea almost 10 years
    Closures for swift don't appear to work as I would expect. The only way I could get the inside value to "stick" outside the closure is if the placemark was a member variable of the controller. It would probably work with static/class variables if swift supported them (apparently not available in the beta). Note, I even tried to use a local array initialized outside of the closure. I added (appended) the placemark to the array in the closure. But outside the closure the array was still employ.
  • pxpgraphics
    pxpgraphics almost 10 years
    Can you elaborate on how you've solved your issue? I've tried external functions and notifications and still am receiving EXC_BAD_ACCESS. Thanks in advance, this would help other devs transitioning to Swift.
  • pxpgraphics
    pxpgraphics almost 10 years
    sorry, I was using this syntax... self!.geocoder.reverseGeocodeLocation(location, completionHandler:{ [weak self] (placemarks: [AnyObject]!, error: NSError!) -> Void in // Your code here. })
  • AaronDancer
    AaronDancer almost 10 years
    Sure, I'll edit this post and add my current code and elaborate a bit more.
  • SomaMan
    SomaMan over 6 years
    This will still return nil - you're not realising that p is set after you return the value, as reverseGeocodeLocation works async