0

I have following function geocoding address from GPS coordinates

private func getAddressFromLocation(forLocation: CLLocationCoordinate2D) {
    let geoCoder = CLGeocoder()
    geoCoder.reverseGeocodeLocation(CLLocation(latitude: forLocation.latitude, longitude: forLocation.longitude), completionHandler: {(places, error) in
        guard error == nil else {
            return
        }
        let place: CLPlacemark = places!.first!
        var address: [String] = []
        if place.country != nil { address.append(place.country!) }
        if place.postalCode != nil { address.append(place.postalCode!) }
        if place.locality != nil { address.append(place.locality!) }
        if place.thoroughfare != nil { address.append(place.thoroughfare!) }
        self.fullAddress = address.joined(separator: ",")
    })
}

Function is called from another part of my application and I'm wondering how to ensure that program will wait until fullAddress variable got value (function finish). I tried to put function call into sync dispatch queue but it didn't help. Thanks for any suggestion.

jnpdx
  • 45,847
  • 6
  • 64
  • 94
Dawy
  • 770
  • 6
  • 23

2 Answers2

3

I would:

  • use completion handlers with Result type;
  • decouple the updating of a UI from the reverse geocoding;
  • use CNPostalAddressFormatter from Contacts framework to format the address;

E.g.


enum GeocodeError: Error {
    case notFound
}

let geocoder = CLGeocoder()

func getAddress(for coordinate: CLLocationCoordinate2D, completion: @escaping (Result<String, Error>) -> Void) {
    let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
    geocoder.reverseGeocodeLocation(location) { places, error in
        guard error == nil, let address = places?.first?.postalAddress else {
            completion(.failure(error ?? GeocodeError.notFound))
            return
        }

        let formatter = CNPostalAddressFormatter()
        let string = formatter.string(from: address)
            .components(separatedBy: "\n")
            .joined(separator: ", ")
        completion(.success(string))
    }
}

And then call it like so:

getAddress(for: coordinate) { result in
    switch result {
    case .failure(let error):   // update UI to report error, if any
    case .success(let address): // update UI to show address
    }
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
0

yes, completion handlers are going to be your friend here:

typealias CompletionHandler = (_ success:Bool) -> Void

private func getAddressFromLocation(forLocation: CLLocationCoordinate2D, completionHandler: @escaping CompletionHandler) {
    let geoCoder = CLGeocoder()
    geoCoder.reverseGeocodeLocation(CLLocation(latitude: forLocation.latitude, longitude: forLocation.longitude), completionHandler: {(places, error) in
        guard error == nil else {
            completionHandler(false)
            return
        }
        let place: CLPlacemark = places!.first!
        var address: [String] = []
        if place.country != nil { address.append(place.country!) }
        if place.postalCode != nil { address.append(place.postalCode!) }
        if place.locality != nil { address.append(place.locality!) }
        if place.thoroughfare != nil { address.append(place.thoroughfare!) }
        print("address: \(address.joined(separator: ","))")
        completionHandler(true)
    })
}

You can call this like so:

getAddressFromLocation(forLocation: CLLocationCoordinate2D()) { success in
    if success {
        //once address is fetched, this will be triggered.
    }
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
Brendan C
  • 16
  • 2