用户授权位置使用时的双重 segue 行为

Double segue behavior when user authorizes location use

在我的 iOS 应用程序中,我请求用户允许获取设备的位置。在我的主 ViewController 上,我有一个导航栏按钮,当点击该按钮时,它会在使用时询问用户许可。如果用户点击确定,它将被发送到显示本地数据的视图控制器。如果用户点击取消,则什么也不会发生。我还有一个弹出窗口,说明用户何时以及是否再次点击位置按钮以重定向到设置以授权位置使用(如果之前已取消)。

该应用程序在模拟器中按预期运行,但在设备上使用时,当用户点击“确定”以允许使用位置时,它会转到本地视图控制器,但会连续执行 2 或 3 次。

segue 从主视图控制器转到本地视图控制器,它使用 IBAction 从按钮点击请求权限。

位置信息在主视图控制器中获取并传递给本地控制器。本地控制器按预期显示所有内容。

如何防止对同一个 View Controller 进行双重或三次 segue?

下面是我的代码:

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()
    }
}

这里的问题是您正在

中执行 segue
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])

可以多次触发(每次来自 CLLocationManager 的新位置)。有几种方法可以解决这个问题,但为了让您尽可能少地进行更改,我建议这样做:

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:
        //-- MODIFIED HERE --
        locationManager.stopUpdatingLocation()
        locationManager = nil
        Dispatch.main.async {
            self.performSegue(withIdentifier: "toLocal", sender: nil)
        }
    }
}

如果有帮助请告诉我,否则我们可以稍微更改您的代码来解决这个简单的问题。

编辑:如你所知:最好在

中执行 segue
private func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus)

当状态为 kCLAuthorizationStatusAuthorizedkCLAuthorizationStatusAuthorizedAlwayskCLAuthorizationStatusAuthorizedWhenInUse 时,取决于您的需要。

superpuccio 所述,主要问题是 didUpdateLocations 委托函数被多次调用。我也不知道您为什么要检查 didUpdateLocations 函数中的 authorizationStatus,因为此时已经很清楚用户允许位置访问。在我看来,该函数应如下所示:

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)
}

由于还有一些问题,例如在知道实际授权状态之前就开始位置更新,我将像我一样提供完整的解决方案。有什么不明白的地方欢迎提问:

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)
    }

}