23

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.

Shruti Thombre
  • 1,009
  • 3
  • 11
  • 27
AaronDancer
  • 640
  • 1
  • 5
  • 21

6 Answers6

24

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.

Community
  • 1
  • 1
AaronDancer
  • 640
  • 1
  • 5
  • 21
  • 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 Jul 09 '14 at 18:33
  • sorry, I was using this syntax... `self!.geocoder.reverseGeocodeLocation(location, completionHandler:{ [weak self] (placemarks: [AnyObject]!, error: NSError!) -> Void in // Your code here. }) ` – pxpgraphics Jul 09 '14 at 23:38
  • Sure, I'll edit this post and add my current code and elaborate a bit more. – AaronDancer Jul 09 '14 at 23:52
15

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!

Goon Nguyen
  • 1,329
  • 9
  • 22
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.

JohnInSea
  • 39
  • 3
  • 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 Jun 22 '14 at 15:04
0

EDIT: This doesn't work. The value is nil outside the closure -- see comments below

Your p is nil because the closure is capturing it before it is initialized to a reference. To get the behavior you want you need to make p a non-optional value such as var p : CLPlacemark!.

Below is code I used to test my conjecture:

 func locationManager(manager: CLLocationManager!, didUpdateToLocation newLocation: CLLocation!, fromLocation oldLocation: CLLocation!) {
    var g = CLGeocoder()
    var p:CLPlacemark?
    let mynil = "empty"
    g.reverseGeocodeLocation(newLocation, completionHandler: {
        (placemarks, error) in
        let pm = placemarks as? CLPlacemark[]
        if (pm && pm?.count > 0){
           // p = CLPlacemark()
            p = CLPlacemark(placemark: pm?[0] as CLPlacemark)

            println("Inside what is in p: \(p?.country ? p?.country : mynil)")

        }

        })

    println("Outside what is in p: \(p?.country ? p?.country : mynil)")

}

Here is console log:

Pushit <- button pressed to start location capturing
Outside what is in p: empty
Inside what is in p: United States
Outside what is in p: empty
Inside what is in p: United States
Outside what is in p: empty...

JohnInSea
  • 39
  • 3
  • I've tried that as well however I get EXC_BAD_ACCESS when doing so. – AaronDancer Jun 22 '14 at 20:11
  • I've tried it again, I'm getting the same result as your example above when using a non-optional value. – AaronDancer Jun 23 '14 at 18:48
  • 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. – JohnInSea Jun 24 '14 at 03:20
  • Aaron, your solution below makes perfect sense. Good job. It was fun fooling with this I learned a lot about Swift closures. I would comment in the relevant answer if I was enabled to... :) – JohnInSea Jun 24 '14 at 03:30
  • As of Beta 3, Array types are now written with the brackets around the element type... Change: `let pm = placemarks as? CLPlacemark[]` to: `let pm = placemarks as? [CLPlacemark]` – pxpgraphics Jul 09 '14 at 23:35
  • Yep. Noticed that yesterday. Quite strange. I'll make an edit – AaronDancer Jul 09 '14 at 23:52
0

Bit late to this party, but it looks like you need(ed) to do some ground-up reading about async stuff. Saying that, you've probably learnt it by now.

The basic problem with your code is that p (your placemark) is being set after the function returns, so it's just lost - you can't use a function to return a value with async. With a completion closure, your code is passed the placemark when it arrives (asynchronously) & the closure is invoked - note the function is now returning nothing.

func getPlacemarkFromLocation(_ location: CLLocation, completion: ((CLPlacemark?) -> ())) {

    CLGeocoder().reverseGeocodeLocation(location, completionHandler: { (placemarks, error) in
        // use optional chaining to safely return a value or nil
        // also using .first rather than checking the count & getting placemarks[0] -
        // if the count is zero, will just give you nil
        // probably a good idea to check for errors too
        completion(placemarks?.first)
    })
}

Use -

getPlacemarkFromLocation(myLocation, completion: { (placemark) in
    // do something with the placemark here
})

I've not actually put this into Xcode, but it looks right...

SomaMan
  • 4,028
  • 1
  • 30
  • 45
-4

Your stuff doesn't work for a number of reasons. Here's the part that I fixed without actually looking at the functionality:

class func getPlacemarkFromLocation(location:CLLocation)->CLPlacemark?{
    var g = CLGeocoder()
    var p:CLPlacemark?
    g.reverseGeocodeLocation(location, completionHandler: {
        (placemarks, error) in
        let pm = placemarks!
        if (pm.count > 0){
            p = placemarks![0]
        }
    })
    return p
}
NSNoob
  • 5,320
  • 5
  • 35
  • 51
Ryan Dines
  • 871
  • 8
  • 18
  • This will still return nil - you're not realising that p is set after you return the value, as reverseGeocodeLocation works async – SomaMan Jan 24 '18 at 14:30