iOSで位置情報やモーションを使うのときの権限要求あれこれ

仕事でiOSアプリを作っていて、妙なところでハマってしまったので備忘録までに。

* 位置情報やモーション フィットネスを使うための事前設定
* 任意のタイミングで許諾を求める処理
* アプリを開いたときの権限不足チェック

# 位置情報やモーション フィットネスを使うための事前設定
アプリを作成したら、info.plistに位置情報とモーションの項目を追加します
![スクリーンショット 2021-11-30 7.25.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/9abfc324-4fdc-ce2d-7b56-bbf99f643411.png)

Targets > Signing&Capabilitiesの左上の「+Capability」からBackgroundModesを選択すると項目が追加されます
![スクリーンショット 2021-11-30 7.25.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/a9932595-0e8a-db67-6911-6a5e1bfe09e3.png)

バックグラウンドでアクセスする処理を追加しておきます
![スクリーンショット 2021-11-30 7.26.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/572a9c11-ddb1-daf0-be93-3683463eed75.png)


これにてアプリ側の初期設定は終了かなと思います


# 任意のタイミングで許諾を求める処理
例えばウォークスルーの途中など、ユーザとコミュニケーションを撮っている流れの中で許諾を取りたい場面もあるかと思います

```SceneDelegate.swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate, CLLocationManagerDelegate { // CLLocationManagerDelegate
    private let locationManager = CLLocationManager()
    private let motionManager = CMMotionActivityManager()

    var window: UIWindow?


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
        guard let _ = (scene as? UIWindowScene) else { return }
        
        self.locationManager.requestAlwaysAuthorization()
        self.locationManager.delegate = self
        self.motionManager.startActivityUpdates(
            to: OperationQueue.current!) { (data: CMMotionActivity?) in
                /* do nothing */
            }
        self.motionManager.stopActivityUpdates()
        
    }

    〜 略 〜
}

```

ハマったポイントとしては、

* 位置情報のアクセスを求めるときは、delegateをしないといけない
* モーションには位置情報のように権限リクエストするメソッドが見当たらず、一瞬start/stopして動かしている

![boot_location.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/99d5ed8f-a43c-b52d-49ef-c21e91728653.png) ![boot_motion.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/6380ffa5-c871-0de6-ac2c-6d9c56e6a1da.png)

アプリを起動すると、位置情報アクセスとモーションフィットネスへのアクセスを要求するダイアログが出るようになりました

# アプリを開いたときの権限チェック
アプリを使っている途中やウォークスルーで許可しそびれたときに、アプリ側から権限チェックをする機能です。アプリを起動して最前面にもっていくごとにチェックするため、SceneDelegateからダイアログを表示しています

```SceneDelegate.swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate, CLLocationManagerDelegate {
    private let locationManager = CLLocationManager()
    private let motionManager = CMMotionActivityManager()

    〜 略 〜

    func sceneDidBecomeActive(_ scene: UIScene) {
        if #available(iOS 14.0, *) {
            if locationManager.authorizationStatus != .authorizedAlways {
                showPermitDialog(title: "権限が不足しています", body: "位置情報アクセスを「常に許可」にしてください")
            }
            if locationManager.accuracyAuthorization != .fullAccuracy {
                showPermitDialog(title: "権限が不足しています", body: "正確な位置情報を許可にしてください")
            }
        } else {
            if CLLocationManager.authorizationStatus() != .authorizedAlways {
                showPermitDialog(title: "権限が不足しています", body: "位置情報アクセスを「常に許可」にしてください")
            }
        }
        
        if CMMotionActivityManager.authorizationStatus() != .authorized {
            showPermitDialog(title: "権限が不足しています", body: "モーション&フィットネスを許可にしてください")
        }
        
        if ProcessInfo.processInfo.isLowPowerModeEnabled {
            showPermitDialog(title: "権限が不足しています", body: "低電力モードを無効にしてください")
        }
    }

    private func showPermitDialog(title: String, body: String) {
        let dialog = UIAlertController(title: title, message: body, preferredStyle: .alert)
        dialog.addAction(UIAlertAction(title: "設定を開く", style: .default, handler: {
            (action: UIAlertAction!) -> Void in
            
            if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
                UIApplication.shared.open(url, options: [:], completionHandler: nil)
            }
        }))
        dialog.addAction(UIAlertAction(title: "キャンセル", style: .cancel, handler: nil))
        self.window?.rootViewController?.present(dialog, animated: true, completion: nil)
    }

    〜 略 〜
}
```

![check_permission.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/f6d8be59-fe59-8bd6-db8d-b16f4a2dbb0c.png)