0

First I have a global variable:

var userCity : String? 

Using reverseGeocodeLocation() to obtain the city name from the user's location, I am trying to store that city name information into the userCity global variable. I know that you cannot simply return data from asynchronous tasks. Instead, you would have to pass back via a block. This is what I have so far after looking at other StackOverflow posts:

//get user's location
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    let location = locations[locations.count - 1]

    if location.horizontalAccuracy > 0 {
        locationManager.stopUpdatingLocation()
        locationManager.delegate = nil

        reverseCoordinatesToCity(location: location) { (city) in
            self.userCity = city
        }
    }
}

func reverseCoordinatesToCity(location: CLLocation, completion: @escaping (_ city: String?) -> Void) {

    CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
        if (error != nil) {
            print("Reverse geocoder failed with an error: " + (error?.localizedDescription)!)
            completion("")
        } else if (placemarks?.count)! > 0 {
            let pm = placemarks![0] as CLPlacemark
            completion(pm.locality!)
        } else {
            print("Problems with the data received from geocoder.")
            completion("")
        }
    })
}

When I try to print userCity in a different method. For example:

func someFunction() {
    print(userCity!)
}

Nothing is printed out and it seems that userCity is nil. I've spent almost three hours trying to figure this out. My main goal is to pass this data onto another ViewController using a segue.

Any help is appreciated. Thank you.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • 1
    Is your `reverseCoordinatesToCity` being called? Does it eventually call the completion handler? Be aware that you will get `nil` from `userCity` until `reverseCoordinatesToCity` is actually called and it calls its completion handler. You may simply be trying to print `userCity` too soon. – rmaddy Sep 11 '18 at 02:01
  • Appreciate the response. `reverseCoordinatesToCity` is being called in the `locationManager` func. The completion handler should also be called because along with the `self.userCity = city` statement, I also have another statement `self.cityName.text = city` which changes a UILabel (and it successfully does). `print(userCity!)` is actually in a `prepare(for segue)` function and it prints nothing at the moment. – honeypigmine Sep 11 '18 at 02:35
  • It sounds like you are printing `userCity` too soon. – rmaddy Sep 11 '18 at 02:36
  • What I also don't get is that I can change the UILabel for the city name by `self.cityName.text = city` and this change is reflected when I run the simulator. However, when I try to, alternatively, `print(self.cityName.text)', it still does not print anything in the `prepare(for segue)` function. I just can't seem to save the city name outside of the asynchronous `reverseGeocodeLocation()` function... – honeypigmine Sep 11 '18 at 02:43
  • As I've said twice already, your `print` is happening too soon. But a breakpoint on your `print(userCity)` line and a breakpoint on the `self.userCity = city` line. Run your app. See which breakpoint is reached first. Based on what you've said, it should reach your `print` line first. But it's just a `print`. It doesn't matter. Move the `print` to be inside the completion block if you want to see it work. – rmaddy Sep 11 '18 at 02:47
  • When I have the print statement inside the completion block, it does work. However, when I have it right outside of the completion block, it doesn't. Ultimately, I want to be able to pass on the city name information (retrieved from `reverseGeocodeLocation()`) in the first view controller to a second a view controller. Could you tell me how I can go about doing this? Essentially, I would have to somehow save the city name that I get asynchronously. – honeypigmine Sep 11 '18 at 02:54
  • You are saving the city name. But it's asynchronous. It takes time. You have to wait until it's done. – rmaddy Sep 11 '18 at 03:00

1 Answers1

0

As @rmaddy has said you are not using city when its ready. The way to fix that is to hinge your view controller logic from the CLGeocoder callback. It's also worth setting your location manager to nil if you don't want to use it anymore because stopUpdatingLocations() doesn't always stop location updates dead in its tracks.

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[locations.count - 1]

if location.horizontalAccuracy > 0 {
    locationManager.stopUpdatingLocation()
    locationManager.delegate = nil
    locationManager = nil //this makes sure you don't get more updates

    reverseCoordinatesToCity(location: location)
    }
}

func reverseCoordinatesToCity(location: CLLocation) {

CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
    if (error != nil) {
        print("Reverse geocoder failed with an error: " + (error?.localizedDescription)!)
    } else if (placemarks?.count)! > 0 {
        let pm = placemarks![0] as CLPlacemark
        city = pm.locality!
        //use city here with your view controllers
    } else {
        print("Problems with the data received from geocoder.")
    }
})
}
Stephen O'Connor
  • 1,465
  • 1
  • 10
  • 20