Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift - CLGeocoder reverseGeocodeLocation completionHandler closure

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.

like image 552
AaronDancer Avatar asked Jun 21 '14 19:06

AaronDancer


3 Answers

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.

like image 170
AaronDancer Avatar answered Nov 09 '22 02:11

AaronDancer


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!

like image 30
Goon Nguyen Avatar answered Nov 09 '22 04:11

Goon Nguyen


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.

like image 31
JohnInSea Avatar answered Nov 09 '22 03:11

JohnInSea