Why is my CLLocationManager sometimes updating location twice after startUpdatingLocation is called

269 views Asked by At

Once a button is pressed, I have a CLLocationManager that requests authorization for location services and if the user accepts, the locationManager will call startUpdatingLocation(). Once there is an updated location (which is immediately), I expect the ClLocationManagerDelegate to call didUpdateLocations and from there I immediately call manager.stopUpdatingLocation() so that I ONLY get 1 set of coordinates for the time being. However, sometimes (inconsistently) I will get two sets of coordinates as if the button was pressed twice in succession. I understand startUpdatingLocation can be tricky because it can very rapidly update your location until it is stopped but I can't seem to pinpoint where and how to avoid this! I found many threads on this same issue but nothing that has worked for my specific case.

I looked online and found this and tried a couple of the things in that thread but still could not fix it.

Below is my code:

getUserLocation() is the first function called when the button in my app is pressed.

func getUserLocation() {
    self.places.removeAll()
    self.placesTableView.reloadData()

    LocationService.shared.requestPermissionToAccessLocation()
    LocationService.shared.locationUpdated = { [weak self] location in
        self?.fetchPlaces(location: location)
    }

    self.view.addSubview(placesTableView)
    placesTableView.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate(
        [
            placesTableView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 200),
            placesTableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 150),
            placesTableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -10),
            placesTableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -130),
        ]
    )
}

private func fetchPlaces(location: CLLocationCoordinate2D) {
    let searchSpan = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
    let searchRegion = MKCoordinateRegion(center: location, span: searchSpan)

    let searchRequest = MKLocalSearch.Request()
    searchRequest.region = searchRegion
    searchRequest.resultTypes = .pointOfInterest
    searchRequest.naturalLanguageQuery = "bar"

    let search = MKLocalSearch(request: searchRequest)

    search.start { response, error in
        guard let mapItems = response?.mapItems else {
            return
        }

        DispatchQueue.main.async { [weak self] in
            for item: MKMapItem in mapItems {
                let placeMark = item.placemark as CLPlacemark

                let completeBusinessString = String(format: "%@\n%@ %@\n%@, %@ %@", placeMark.name!, placeMark.subThoroughfare!, placeMark.thoroughfare!, placeMark.locality!, placeMark.administrativeArea!, placeMark.postalCode!)

                self?.places.append(completeBusinessString)
            }
            self?.placesTableView.reloadData()
        }
    }
}

LocationServices singleton class

class LocationService: NSObject {

    static let shared = LocationService()

    lazy var locationManager: CLLocationManager = {
        let manager = CLLocationManager()
        manager.desiredAccuracy = kCLLocationAccuracyBest
        manager.delegate = self
        return manager
    }()

    var locationUpdated: ((CLLocationCoordinate2D) -> Void)?

    override private init() {
        super.init()
    }

    func requestPermissionToAccessLocation() {
        switch locationManager.authorizationStatus {
        case .notDetermined:
            locationManager.requestWhenInUseAuthorization()
        case .restricted:
            locationManager.requestWhenInUseAuthorization()
        case .denied:
            break
        case .authorizedAlways:
            locationManager.startUpdatingLocation()
        case .authorizedWhenInUse:
            locationManager.startUpdatingLocation()
        default:
            break
        }
    }

}

extension LocationService: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        manager.stopUpdatingLocation()
        if let location = locations.last?.coordinate {
            print(location)
            locationUpdated?(location)
        }
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error.localizedDescription)
    }

    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        switch manager.authorizationStatus {
        case .authorizedAlways:
            manager.startUpdatingLocation()
        case .authorizedWhenInUse:
            manager.startUpdatingLocation()
        default:
            break
        }
    }

}
1

There are 1 answers

2
matt On

If the goal is to get just one value that tells you the location, you're making the wrong call; use requestLocation.