Search code examples
swiftblockcllocationcompletionhandler

How To Call a func within a Closure


In a model's class Location, I get the name of the current city:

var currentLatitude: Double!
var currentLongitude: Double!
var currentLocation: String!
var currentCity: String!

func getLocationName() {

    let geoCoder = CLGeocoder()
    let location = CLLocation(latitude: currentLatitude, longitude: currentLongitude)

    geoCoder.reverseGeocodeLocation(location, completionHandler: { placemarks, error in
        guard let addressDict = placemarks?[0].addressDictionary else {
            return
        }
        if let city = addressDict["City"] as? String {
            self.currentCity = city
            print(city)
        }
        if let zip = addressDict["ZIP"] as? String {
            print(zip)
        }
        if let country = addressDict["Country"] as? String {
            print(country)
        }

        self.nowUpdateUI()
    })
}

In view controller I want to update the UI and update my label to show the current city. However, self.currentCity = city happens inside of a closure. So if I just run a func in view controller:

func updateUI() {
        cityLbl.text = Location.sharedInstance.currentCity
}
  • I'm not getting anywhere because the closure haven't finished running. I've been advised to add a completion handler to getLocationName() and inside of it, perform the call to a func that will update the UI. However, from all the tutorials out there on closures, completion handlers, it is not clear to me how to achieve that. How to construct a completion handler, pass it as an arg to getLocationName() and how to call getLocationName from view controller?

Solution

  • To handle this situation you have multiple option.

    1. Create delegate/protocol with your Location class

      • Create one protocol and implement that protocol method with your ViewController and declare its instance in your Location class. After then in the completionHandler of reverseGeocodeLocation call this delegate method. Check Apple documentation on Protocol for more details.
    2. You can create completionHandler with your getLocationName method of Location class.

      • Add completionHandler with getLocationName and called that completionHandler inside the completionHandler of reverseGeocodeLocation like this way.

        func getLocationName(completionHandler: @escaping (_ success: Bool) -> Void) {
        
            let geoCoder = CLGeocoder()
            let location = CLLocation(latitude: currentLatitude, longitude: currentLongitude)
        
            geoCoder.reverseGeocodeLocation(location, completionHandler: { placemarks, error in
                guard let addressDict = placemarks?[0].addressDictionary else {
                    completionHandler(false)
                    return
                }
                if let city = addressDict["City"] as? String {
                    self.currentCity = city
                    print(city)
                }
                if let zip = addressDict["ZIP"] as? String {
                    print(zip)
                }
                if let country = addressDict["Country"] as? String {
                    print(country)
                }
                completionHandler(true)
                //self.nowUpdateUI()
            })
        }
        

        Now in ViewController where you are calling this function call your updateUI method inside the completion block.

        Location.sharedInstance.getLocationName { (success) in
            if success {//If successfully got response
                self.updateUI()
            }
        }
        
    3. You can add observer for (NS)NotificationCenter.

      • Register the observer with (NS)NotificationCenter and then post the notification inside the completionHandler of reverseGeocodeLocation. You can get more detail on this with this StackOverflow Post.