In my iOS application I’m requesting the user’s permission to obtain the device’s location. On my main ViewController I have a navigation bar button that when tapped, it will ask the user for permission for when in use. If the user taps OK, it will then be send to the view controller that displays the local data. If the user taps Cancel then nothing happens. I also have a pop up for when and if the user taps again on the location button to be redirected to the settings to authorize location use if previously cancelled.
The app works as I intended in the simulator but when used on a device, when the user taps OK to allow location use, it segues to the local View Controller but it does so 2 or 3 times consecutively.
The segue goes from the main view controller to the local view controller and it requests permissions from the button tap using an IBAction.
The location information is obtained in the main view controller and passed to the local controller. The local controller displays everything as it is intended.
How can I prevent this double or triple segue to the same View Controller?
Below is my code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toLocal" {
let destination = segue.destination as! LocalViewController
destination.latitude = latitude
destination.longitude = longitude
}
}
//MARK: - Location Manager Methods
@IBAction func LocationNavBarItemWasTapped(sender: AnyObject) {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[locations.count - 1]
if location.horizontalAccuracy > 0 {
locationManager.stopUpdatingLocation()
latitude = location.coordinate.latitude
longitude = location.coordinate.longitude
}
let status = CLLocationManager.authorizationStatus()
switch status {
case .restricted, .denied:
showLocationDisabledPopUp()
return
case .notDetermined:
// Request Access
locationManager.requestWhenInUseAuthorization()
case .authorizedAlways:
print("Do Nothing: authorizedAlways")
case .authorizedWhenInUse:
self.performSegue(withIdentifier: "toLocal", sender: nil)
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("LocationManager failed with error \(error)")
}
private func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if (status == CLAuthorizationStatus.denied) {
showLocationDisabledPopUp()
}
}
As superpuccio already stated the main issue is that the didUpdateLocations
delegate function is called multiple times. I also do not know why you are checking the authorizationStatus
in the didUpdateLocations
function since at that point it is already clear that the user allowed location access. In my opinion the function should look something like this:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// check for a location that suits your needs
guard let location = locations.last, location.horizontalAccuracy > 0 else { return }
// prevent the manager from updating the location and sending more location events
manager.delegate = nil
manager.stopUpdatingLocation()
// update the local variables
latitude = location.coordinate.latitude
longitude = location.coordinate.longitude
// perform the segue
performSegue(withIdentifier: "toLocal", sender: nil)
}
Since there are some more issues like starting location updates before knowing the actual authorization status I'll provide a full solution like I'd do it. Feel free to ask if anything is unclear:
class ViewController: UIViewController {
var latitude: CLLocationDegrees?
var longitude: CLLocationDegrees?
lazy var locationManager: CLLocationManager = {
let locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
return locationManager
}()
@IBAction func getLocation(_ sender: UIBarButtonItem) {
locationManager.delegate = self
checkAuthorizationStatus()
}
private func checkAuthorizationStatus(_ status: CLAuthorizationStatus? = nil) {
switch status ?? CLLocationManager.authorizationStatus() {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .authorizedWhenInUse:
locationManager.startUpdatingLocation()
default:
showLocationDisabledPopUp()
}
}
func showLocationDisabledPopUp() {
// your popup code
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// your segue code
}
}
extension ViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
checkAuthorizationStatus(status)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last, location.horizontalAccuracy > 0 else { return }
manager.delegate = nil
manager.stopUpdatingLocation()
latitude = location.coordinate.latitude
longitude = location.coordinate.longitude
performSegue(withIdentifier: "toLocal", sender: nil)
}
}