2

I have a locationManager function to grab the users current location and posting the name of the city and state. I have a print statement so I can check in my console if everything is working properly...and it is. However, it prints the city location 3 times. This actually causes an issue in my actual app but thats beyond the point of this question.

My function is as follows:

var usersLocation: String!

var locationManager: CLLocationManager!

func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

    let userLocation: CLLocation = locations[0]


    CLGeocoder().reverseGeocodeLocation(userLocation) { (placemarks, error) -> Void in

        if error != nil {

            print(error)

        } else {

            let p = placemarks?.first // ".first" returns the first element in the collection, or nil if its empty
            // this code above will equal the first element in the placemarks array

            let city = p?.locality != nil ? p?.locality : ""
            let state = p?.administrativeArea != nil ? p?.administrativeArea : ""

            self.navigationBar.title = ("\(city!), \(state!)")
            self.usersLocation = ("\(city!), \(state!)")
            self.locationManager.stopUpdatingLocation()
            print(self.usersLocation)
            self.refreshPosts()
        }
    }
}

So in my print(self.usersLocation) it will print in my console three times. Is this normal?


UPDATE TO SHOW VIEWDIDLOAD


override func viewDidLoad() {
    super.viewDidLoad()

    locationManager = CLLocationManager()
    locationManager.delegate = self
    locationManager.desiredAccuracy = kCLLocationAccuracyBest
    locationManager.requestWhenInUseAuthorization()
    locationManager.startUpdatingLocation()

    self.tableView.rowHeight = UITableViewAutomaticDimension
    self.tableView.estimatedRowHeight = 250.0
}
Matty
  • 343
  • 1
  • 5
  • 16
  • 1
    You have a debugger. Debug! Put a breakpoint at your `print` statement so that you pause there each time. Now you can look at the _call stack_ and see why your `didUpdateLocations` is being called. – matt Dec 02 '15 at 01:43
  • @matt I should mention that I'm a total noob at coding. I understand how to put a break point so that it stops but where can I find this call stack and go about debugging this issue? Thanks. – Matty Dec 02 '15 at 01:45
  • You'll see the call stack in the debug navigator when you hit the breakpoint. – matt Dec 02 '15 at 01:46

2 Answers2

6

I'd first suggest a few things:

  1. Call stopUpdatingLocation before you perform reverseGeocodeLocation.

    You are calling stopUpdatingLocation inside the reverseGeocodeLocation completion handler closure. The problem is that this runs asynchronously, and thus didUpdateLocations may receive additional location updates in the intervening period. And often, when you first start location services, you'll get a number of updates, often with increasing accuracy (e.g. horizontalAccuracy values that are smaller and smaller). If you turn off location services before initiating asynchronous geocode request, you'll minimize this issue.

  2. You can also add add a distanceFilter in viewDidLoad, which will minimize redundant calls to the delegate method:

    locationManager.distanceFilter = 1000
    
  3. You can use your own state variable that checks to see if the reverse geocode process has been initiated. For example:

    private var didPerformGeocode = false
    
    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        // if we don't have a valid location, exit
    
        guard let location = locations.first where location.horizontalAccuracy >= 0 else { return }
    
        // or if we have already searched, return
    
        guard !didPerformGeocode else { return }
    
        // otherwise, update state variable, stop location services and start geocode
    
        didPerformGeocode = true
        locationManager.stopUpdatingLocation()
    
        CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in
            let placemark = placemarks?.first
    
            // if there's an error or no placemark, then exit
    
            guard error == nil && placemark != nil else {
                print(error)
                return
            }
    
            let city = placemark?.locality ?? ""
            let state = placemark?.administrativeArea ?? ""
    
            self.navigationBar.title = ("\(city), \(state)")
            self.usersLocation = ("\(city), \(state)")
            print(self.usersLocation)
            self.refreshPosts()
        }
    }
    
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • thank you Rob, much appreciated. I never realized it was an asynchronous situation with my code. – Matty Dec 02 '15 at 03:53
  • Hi Rob, I simply moved my `stopUpdatingLocation` above my `reverseGeocodeLocation` however it still outputs the city three times after I move away from that view controller and come back to it. For example, upon launching the app it would load fine. However, once I click a button to another view controller then go back to my home page, it will output the city three times again...any suggestions to fix this issue? – Matty Dec 02 '15 at 04:01
  • i also updated to show my `viewDidLoad` if that will help my case at all! Thanks – Matty Dec 02 '15 at 04:32
  • I'm a little surprised by that behavior (you're saying that you're seeing `didUpdateLocations` continue to be called after you've called `stopUpdatingLocation` ... that's surprising because I see lots of people implement this pattern), but adding your own state variable to determine whether you already initiated a geocoding process would likely solve that. – Rob Dec 02 '15 at 06:39
  • Would you kindly be able to clarify how I would go about adding my own state variable to determine whether or not I initiated a geocoding process? I'm somewhat new to programming so it would be of great help! – Matty Dec 02 '15 at 07:02
  • also, how exactly would I check to see if `didUpdateLocations` continue to be called other than looking at my console to see that it prints out my custom location 3 times in a row? Not sure if i understand the "call stack" entirely – Matty Dec 02 '15 at 07:05
  • 1
    @Matty - I take that back: I do see `didUpdateLocations` called three times, just like you. I usually don't because I usually employ a non-zero `locationManager.distanceFilter` that prevents these redundant updates. In terms of the checking of the call stack (that's usually a technique for tracking down a crash, but that doesn't seem like the problem here). If you wanted to check to see how often `viewDidLoad` or `didUpdateLocations` were called, though, just add a `print` statement in there. Anyway, see revised answer that clarifies some of my findings. – Rob Dec 02 '15 at 07:44
  • thank you so much. Your code seems to have fixed my issue. Now the next challenge is to understand exactly what you have done :) . Thank you again for your time – Matty Dec 02 '15 at 15:31
0

I had the same problem and Rob's answer didn't do it for me.

When the location service first starts, the location is updated multiple times regardless of the distanceFilter.

You might still want the location to be updated and you don't want to lose the location accuracy(which is the whole point of updating location multiple times on start-up), so calling stopUpdatingLocation(or using a local variable) after the first geolocating call isn't the way to go either.

The most intuitive way is to wrap your geocode call in an @objc function and call the the function with a delay:

NSObject.cancelPreviousPerformRequests(withTarget: self)
perform(#selector(myGeocodeFunction(_:)), with: location, afterDelay: 0.5)
jc127
  • 343
  • 2
  • 13