0

I am new to swift and I can't figure out how to handle closures and closures concept.

I recently asked question and I find out that my variable is nil because geocodeAddressString runs asynchronously so app printing latLong well before this property was eventually set.

But here is new question that I can't understand:

import UIKit
import CoreLocation
import Firebase

var latLong: String!

override func viewDidLoad() {
    super.viewDidLoad()

}

func findCordiante(adress:String){

    let geocoder = CLGeocoder()
    geocoder.geocodeAddressString(adress) {
        placemarks, error in

        if (placemarks != nil){
            let placemark = placemarks?.first
            let lat = placemark?.location?.coordinate.latitude
            let lon = placemark?.location?.coordinate.longitude

            self.latLong = String(describing: lat!) + "," + String(describing: lon!)

        }else{
            //handle no adress
             self.latLong = ""
        }
    }

}

@IBAction func createSchool(_ sender: Any) {

      //when user press button i want execute function and assign value to variable latLong  
      findCordiante(adress: "Cupertino, California, U.S.")
      //so then I need to manipulate with that value here, let's say example

      //example
      print("Hi user, your coordinates is \(latLong)")

}

When I add print(latLong) inside closure it is printing, but I DONT WANT to do all functionality inside closure.

Simply I WANT to add result of func findCordiante() to variable latLong, so after that I can manipulate with that variable everywhere inside class

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Sven
  • 63
  • 1
  • 11
  • Possible duplicate of [Swift closure async order of execution](https://stackoverflow.com/questions/39880506/swift-closure-async-order-of-execution) – jscs Dec 25 '17 at 16:14
  • You can't. You have to wait for the result – jscs Dec 25 '17 at 16:14

3 Answers3

1

The main thing to understand is that resolving an address into coordinates (and many other geolocation operations) take time and therefore return a result with considerable delay. During the delay, the app continues to run and should be responsive to user actions.

That's the reason why closures are used, namely to split the operation into two parts:

  • Start the operation (your findCoordinate function)
  • Complete the action after the operation has finished (the closure, used as a callback)

Between these two parts, your application runs normally. It does not wait or block. If you want a waiting behaviour, you have to implement it yourself (e.g. disable buttons, ignore user gestures etc.(.

You can easily move part of the code within the closure into a separate function:

func findCordiante(adress:String){

    let geocoder = CLGeocoder()
    geocoder.geocodeAddressString(adress) {
        placemarks, error in

        if let placemarks = placemarks {
            self.findCoordinateCompleted(placemarks)
        } else {
            self.findCoordinateFailed()
        }
    }
}

func findCoordinateCompleted(placemarks: [CLPlacemark]) {
    let placemark = placemarks.first!
    let lat = placemark.location!.coordinate.latitude
    let lon = placemark.location!.coordinate.longitude
    latLong = String(describing: lat) + "," + String(describing: lon)

    completeCreatingSchool()
}

func findCoordinateFailed() {
    latLong = ""
    print("Hi user, invalid address")
    // do more stuff here
}

@IBAction func createSchool(_ sender: Any) {
    findCoordinate(adress: "Cupertino, California, U.S.")
}

func completeCreatingSchool() {
    //example
    print("Hi user, your coordinates is \(latLong)")
}
Codo
  • 75,595
  • 17
  • 168
  • 206
0

After you set latLong in the closure, it will be available to the rest of your class. The only problem you have is if createSchool gets called before the closure is done.

Solution to this is to have the button and/or menu item that points to createSchool start out disabled. You then enable it after the closure finishes.

Charles Srstka
  • 16,665
  • 3
  • 34
  • 60
0

The closest solution according to you code is to use a completion handler and return a boolean value success in the closure. Then you can use the latLong variable (wouldn't be CLLocationCoordinate2D the better type?) right after setting it.

func findCoordinate(adress:String, completion: @escaping (Bool)->()){

    let geocoder = CLGeocoder()
    geocoder.geocodeAddressString(adress) {
        placemarks, error in

        if (placemarks != nil){
            let placemark = placemarks?.first
            let lat = placemark?.location?.coordinate.latitude
            let lon = placemark?.location?.coordinate.longitude

            self.latLong = String(describing: lat!) + "," + String(describing: lon!)
            completion(true)
        }else{
            //handle no adress
             self.latLong = ""
             completion(false)
        }
    }

}

@IBAction func createSchool(_ sender: Any) {

   //when user press button i want execute function and assign value to variable latLong  
   findCoordinate(adress: "Cupertino, California, U.S.") { success in
       //so then I need to manipulate with that value here, let's say example

       //example
       if success {
          print("Hi user, your coordinates is \(latLong)")
       }
   }
}
vadian
  • 274,689
  • 30
  • 353
  • 361