0

so I am calling a function that should just return the longitude of an inputted address. I'll append it here so you can look at it and then I will comment on it after the code:

func getLongitude(address: String) -> Double {
    var longitude: Double = 0.0
    let geocoder = CLGeocoder()
    geocoder.geocodeAddressString(address) {
        placemarks, error in 
        let placemark = placemarks?.first
        longitude = placemark?.location?.coordinate.longitude ?? 0.0
        print("The testing longitude is \(longitude)")
    }
    return longitude
}

As you can see, I declared the longitude variable with a default value of 0.0. In the closure inside the function, I update longitude. The print statement confirms that the longitude was updated in that closure of the function. When longitude is accessed with the return statement outside of this closure, it returns 0.0 again (I believe the value of longitude is not mutated outside of the closure, but I don't know how to fix this). Any help would be much appreciated!

vadian
  • 274,689
  • 30
  • 353
  • 361
nickcoding
  • 305
  • 8
  • 35
  • 3
    http://www.programmingios.net/returning-a-value-from-asynchronous-code/ – matt Apr 25 '20 at 16:56
  • Hi matt, I'm running into a small issue here. So the getLongitude function has to return a double. But the built in function I am using gecodeAddressString utilizes a callback function with a void return type. How can I return something in the geocodeAddressString callback function when its return type is void? – nickcoding Apr 25 '20 at 23:30
  • 1
    Reread the article! You can't return a double. You need to rewrite your `getLongitude` so that _it_ takes _a completion handler_ that takes a double. In the `geocodeAddressString` completion handler, you _call_ that completion handler, passing it the double. – matt Apr 25 '20 at 23:40

2 Answers2

1

Please use tabs to make your code easier readable.

Anyway geocoder.geocodeAddressString(address) is a method with a callback and in this callback method you have the placemark. Your return will not wait for that callback since it will take time to calculate the coordinates hence it returns 0.0 which you set at the start.


Edit: longer version since you asked in a comment:

CLGeocoder()'s function geocodeAddressString has in fact 2 parameters: the adress and a socalled callback. A callback is simply a method called when the task (in this case calcualting the placemark) finishes. Swift lets you write the callback in "swifty" syntax but actually it looks like

geocoder.geocodeAddressString(address, callAfterFinishGeocoding)

func callAfterFinishGeocoding(_ placemark: Placemark) {
   // do stuff with placemark
}

as you can see we pass the geocode function another function to be called when done. The parameters of callAfterFinishGeocoding are defined in geocodeAddressString. It will look similar to this:

callback: @escaping (_ placeMark: Placemark) -> Void

This would mean the callback should be a function accepting a placemark and returning Void. Jump to the definition of the method see what kind of function it wants as parameter.

also read more here: https://stackoverflow.com/a/46245943/13087977

Deitsch
  • 1,610
  • 14
  • 28
  • So I believe I could use Andrey's code...and then when I make the call to the getLongitude function, it would look something like getLongitude(address: addressString, completion: (doubleBeingPassedIn: Double) -> Double { return doubleBeingPassedIn }, right? or is that completely wrong for the argument that should be given to completion when the getLongitude function is called? UPDATE: I tried to pass in that value to completion and got the errors Expected type after '->' and use of unresolved identifier 'valueBeingPassedIn' – nickcoding Apr 25 '20 at 19:10
  • Also I'm sorry I'm so bad at Swift, I've just never used a callback function in any language in my life – nickcoding Apr 25 '20 at 19:13
  • Yes Andreys Code is right. call getLongitude() and pass it a function you want to execute with the longitude. The function you pass has to accept a Double and return Void. – Deitsch Apr 25 '20 at 19:13
  • Don't feel bad, neither did i get it at the beginning. Once it "clicks" you'll get it – Deitsch Apr 25 '20 at 19:15
  • Any idea why getLongitude(address: addressString, completion: { (valueBeingPassedIn: Double) -> Void in return valueBeingPassedIn} ) isn't working? That's how I'm making the call and I'm just getting a warning that underlines the return statement and says "Expression of type 'Double' is unused.' I should be printing the longitude in the call statement and it's just printing '()' instead. :( – nickcoding Apr 25 '20 at 20:06
  • It's hard if i don't have my compiler but try this: getLongitude(address: addressString) { longitude in print(longitude) } (i use swifty syntax). I'd recommand to create the function not inline but rather like i did in the example. After you get that working you can try to make the code shorter. By the way there is no "return". Callbacks are just functions calling other functions passing them the result as argument. – Deitsch Apr 25 '20 at 20:38
  • Ah, so this is probably my fault because I never specified, I need this function to return the Double value "longitude", not just print it out. If I call the function with your recommendation it works well but I need the callback function to return the value of longitude (is that even possible?) – nickcoding Apr 25 '20 at 21:18
  • 1
    You can't return a Double (or anything else). This will never work as your operation is async and you can't wait for a "return". You should rethink you structure. The Code you want to execute after the return can also just execute in the callback. – Deitsch Apr 26 '20 at 12:12
0

The closure is executed asynchronously i.e. after the return statement is run. Change your function to

func getLongitude(address: String, completion: @escaping (Double)->Void)
 {
     var longitude: Double = 0.0
     let geocoder = CLGeocoder()

    geocoder.geocodeAddressString(address)
    {
        placemarks, error in
        let placemark = placemarks?.first
        longitude = placemark?.location?.coordinate.longitude ?? 0.0
        print("The testing longitude is \(longitude)")

        completion(longitude)
    }
 }
Andrey Chernukha
  • 21,488
  • 17
  • 97
  • 161
  • HI Andrey, I wasn't the one who downvoted your comment. I am a little bit confused though when it comes to completion handlers in the parameters (I believe that's what you're doing). Is there a reference that you have where I could learn more about his? I'm also just very unsure on how to make the call of this function (i.e. what is the argument I provide for the "completion" parameter). Thank you so much for your time! – nickcoding Apr 25 '20 at 18:39