iOSアプリで位置情報を取得するときに配慮する点をまとめてみた③

前回の続き↓
[iOSアプリで位置情報を取得するときに配慮する点をまとめてみた②](https://qiita.com/reo0612/items/b25318e74a51b61548a0)

#はじめに
iOSアプリで位置情報を取得するときに配慮する点をまとめた内容となっています。

#前回までのあらすじ
前回、`アプリの位置情報サービスの有無`を`バックグラウンド状態`から戻った時も
チェックをしなければいけないと書きました。

アプリが`バックグラウンド状態`から戻った時にある処理をするには`AppDelegate`の`applicationWillEnterForeground(_ application: UIApplication)`で処理するんでしたね。

そして、アラートを表示したいのでViewController内で処理するために`NotificationCenter`を使いました。

#バックグラウンド状態から戻ったときにチェックする(アプリ)

では、実際にコードを書いてみます。
※長くなるので前回のソースコードから一部抜粋

今回も、アラートの表示はこの方法を用います↓
[UIAlertControllerをファイルを分けて実装してみる](https://qiita.com/reo0612/items/b1fa1e11b3792baa72b8)

```swift:ViewController
import UIKit
import CoreLocation

final class ViewController: UIViewController {

   private var locationManager: CLLocationManager = {
        var locationManager = CLLocationManager()
        locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
        locationManager.distanceFilter = 5
        return locationManager
    }()

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
    }
    
    // バックグラウンド状態から戻ってきた時に呼ばれるメソッド(自分で作る)
    @objc private func willEnterForeground() {
        // 端末の位置情報サービスがオンの場合      
        if CLLocationManager.locationServicesEnabled() {
            // 位置情報サービスの認証ステータスを取得
            let status = CLLocationManager.authorizationStatus()

            switch status {

            // 許可しない場合
            case .denied:
                // アラート表示
                Alert.okAlert(vc: self, title: "アプリの位置情報サービスを\nオンにして下さい", message: "OKをタップすると設定アプリに移動します") { (_) in
                    // 設定アプリに画面遷移する
                    UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
                }
                
            default:
                break
            }
        }
    
    // 端末の位置情報サービスがオフの場合
    }else {
        // アラート表示
        Alert.okAlert(vc: self, title: "位置情報サービスを\nオンにして下さい", message: "「設定」アプリ ⇒「プライバシー」⇒「位置情報サービス」からオンにできます") 
    }
}
```

端末の位置情報サービスがオンかオフかを分岐して
`オン`の場合に`アプリの位置情報サービスの有無`をチェックしています。

ビルドしてみると、ちゃんとチェックされてますね↓

※バグ？のせいなのか、たまに位置情報サービスの認証ステータスが`次回確認`になっていますが
本来は`なし`になります。
![ezgif.com-gif-maker3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743730/c07b55d5-5e18-9c65-5242-83a0dcb3ea14.gif)
因みに、この部分は`.denied`だけのケースのみでOKです。

理由は認証ステータスが変更されれば`デリゲートメソッド`が呼ばれますし、`設定アプリ`に画面遷移するのは`.denied`の場合のみで、その時に何もしないで戻った場合をチェックしたいので他の認証ステータスの記述は必要ないです。

ここで、一旦、全体のソースコードを見てみましょう。

```swift:ViewController
import UIKit
import CoreLocation

final class ViewController: UIViewController {

    private var locationManager: CLLocationManager = {
       var locationManager = CLLocationManager()
       locationManager.distanceFilter = 5
       locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
       return locationManager
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        locationManager.requestWhenInUseAuthorization()
        locationManager.delegate = self
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
    }
    
    @objc private func willEnterForeground() {     
        if CLLocationManager.locationServicesEnabled() {
            
            let status = CLLocationManager.authorizationStatus()

            switch status {

            case .denied:
                Alert.okAlert(vc: self, title: "アプリの位置情報サービスを\nオンにして下さい", message: "OKをタップすると設定アプリに移動します") { (_) in
                    UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
                }
                
            default:
                break
            }

    }else {
        Alert.okAlert(vc: self, title: "位置情報サービスを\nオンにして下さい", message: "「設定」アプリ ⇒「プライバシー」⇒「位置情報サービス」からオンにできます") 
    }
  }
}

extension ViewController: CLLocationManagerDelegate {
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
          if CLLocationManager.locationServicesEnabled() {
        
        let status = manager.authorizationStatus
        
        switch status {
        case .authorizedAlways, .authorizedWhenInUse:
            manager.startUpdatingLocation()
            
        case .notDetermined:
            manager.requestWhenInUseAuthorization()
            
        case .denied:
            Alert.okAlert(vc: self, title: "アプリの位置情報サービスを\nオンにして下さい", message: "OKをタップすると設定アプリに移動します") { (_) in
                UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
            }
            
        case .restricted:
            break           
 
        default:
            break
        }
      }else {
         Alert.okAlert(vc: self, title: "位置情報サービスを\nオンにして下さい", message: "「設定」アプリ ⇒「プライバシー」⇒「位置情報サービス」からオンにできます") 
      }
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let gps = manager.location?.coordinate else {
            return
        }
        manager.stopUpdatingLocation()
        let lat = gps.latitude
        let lng = gps.longitude
        print("経度：\(String(describing: lat)), 緯度：\(String(describing: lng))")
    }
}
```
```swift:Alert
import UIKit

final class Alert {
    static func okAlert(vc: UIViewController,title: String, message: String, handler: ((UIAlertAction) -> Void)? = nil) {
        let okAlertVC = UIAlertController(title: title, message: message, preferredStyle: .alert)
        okAlertVC.addAction(UIAlertAction(title: "OK", style: .default, handler: handler))
        vc.present(okAlertVC, animated: true, completion: nil)
    }
}
```

`端末・アプリの位置情報サービスの有無`や、`変更された認証ステータスの処理`等ここまで
色んな所に配慮してきました。

後は、お気づきの方がいらっしゃると思いますが
認証ステータスが`.restricted`の場合は何もしていませんね。

ここは、Appleのドキュメントを見てみると`ペアレンタルコントロール`などの何らかの制限があるために
ユーザーが`認証ステータスを変更できない状態`に呼ばれる認証ステータスとなっています↓
[CLAuthorization Status .restricted](https://developer.apple.com/documentation/corelocation/clauthorizationstatus/restricted)

ずっと`break`にしていましたが、アラートなどでユーザーに伝えたほうがいいので改修しときましょう。

```swift:ViewController
      case .restricted:
          Alert.okAlert(vc: self, title: "位置情報サービスの使用を\n許可されていません", message: "何らかの制限が掛かっています")
```

#iOS14以外のバージョン対応
現状、`iOS14`以降を想定して位置情報の取得をしてきましたが
iOS14以外のバージョンでも位置情報を取得したい場合が開発していると出てくると思います。

その場合、使用するメソッドが違ったり、Appleが非推奨にしているコード等も登場して
多少、複雑になってきます。

では、実際にアプリのバージョンを下げてみて実装していきましょう。

私はこちらの記事を拝見して、iOSのバージョンを下げましたので参考にしてみて下さい↓
[世界一詳細に全部日本語でXcode11で作った新規プロジェクトをiOS12以前で実行できるところまで解説する](https://qiita.com/sakiyamaK/items/cf95112a28e84b4c6dff)

#推奨・非推奨コード
では、これでiOS11 ~ iOS14に対応するアプリとなりました。
実装する前にAppleが`推奨・非推奨しているコード`を紹介していきます。

※私の記事に関係があるコードだけを紹介しますのでご了承ください。

```swift
// CLLocationManagerクラスのインスタンス初期化および、認証ステータスが変更されたら呼ばれるデリゲートメソッド

// 推奨していない iOS 4.2–14.0
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus)

//　推奨している iOS14-
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager)
```

```swift
// 位置情報の認証ステータス取得

// 推奨していない iOS 4.2–14.0
func authorizationStatus() -> CLAuthorizationStatus

// 推奨している iOS14-
var authorizationStatus: CLAuthorizationStatus
```
こんな感じに変更されています。

後は、iOS14から`正確な位置精度`という項目が新たに追加されました。
簡単に言うと、位置情報の精度を正確にするか曖昧にするかを自分で選択できるようになったんですね。

下記のコードのように、選択肢によって分岐することができます↓

```swift
switch locationManager.accuracyAuthorization {

      // 正確な位置情報を取得した場合  
      case .fullAccuracy:
      
      // 曖昧な位置情報を取得した場合 
      case .reducedAccuracy:
                
      default:
          break
}
```
ここは開発するアプリによって変わってきますので、よく吟味した方が良い部分ですね。
こちらの記事が詳しく書いていますので参考にしてみて下さい↓
[iOS14でのCore Location変更点](https://qiita.com/kj_trsm/items/66e07c61ce4a3142832f#%E6%9C%AC%E9%A1%8C)
[iOS 14 でさらに強化された位置情報まわりのプライバシー](https://qiita.com/koogawa/items/6d2b7c50fd1917d8983e)

※今回は、位置情報の取得で配慮する点をまとめた記事なので
あまり正確かどうかは関係ないので紹介だけに留めておきます。

注目してほしい部分は、最初の方で説明した推奨・非推奨のコードです。
iOS14以前のバージョンに対応させるには`非推奨のコード`も使用しなければいけません。

では早速、実装していきます。

#iOS14以前のバージョンに対応した場合の実装
※長いので一部抜粋

```swift:ViewController
import UIKit
import CoreLocation

final class ViewController: UIViewController {

    private var locationManager: CLLocationManager = {
        var locationManager = CLLocationManager()
        locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
        locationManager.distanceFilter = 5
        return locationManager
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        locationManager.requestWhenInUseAuthorization()
        locationManager.delegate = self
    }
}
    // 推奨しているデリゲートメソッド
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        // iOS14以降の場合
        if #available(iOS 14.0, *) {
            if CLLocationManager.locationServicesEnabled() {
                
                let status = manager.authorizationStatus
                
                switch status {
                case .authorizedAlways, .authorizedWhenInUse:
                    manager.startUpdatingLocation()
                    
                case .notDetermined:
                    manager.requestWhenInUseAuthorization()
                    
                case .denied:
                    Alert.okAlert(vc: self, title: "アプリの位置情報サービスを\nオンにして下さい", message: "OKをタップすると設定アプリに移動します") { (_) in
                        UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
                    }
                    
                case .restricted:
                    Alert.okAlert(vc: self, title: "位置情報サービスの使用を\n許可されていません", message: "何らかの制限が掛かっています")
                    
                default:
                    break
                }
            }else {
                Alert.okAlert(vc: self, title: "位置情報サービスを\nオンにして下さい", message: "「設定」アプリ ⇒「プライバシー」⇒「位置情報サービス」からオンにできます")
            }
        }
    }
    // 非推奨のデリゲートメソッド
    //iOS14以前の場合
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        if CLLocationManager.locationServicesEnabled() {
            
            switch status {
            case .authorizedAlways, .authorizedWhenInUse:
                manager.startUpdatingLocation()
                
            case .notDetermined:
                manager.requestWhenInUseAuthorization()
                
            case .denied:
                Alert.okAlert(vc: self, title: "アプリの位置情報サービスを\nオンにして下さい", message: "OKをタップすると設定アプリに移動します") { (_) in
                    UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
                }
                
            case .restricted:
                Alert.okAlert(vc: self, title: "位置情報サービスの使用を\n許可されていません", message: "")
                
            default:
                break
            }
            
        }else {
            Alert.okAlert(vc: self, title: "位置情報サービスを\nオンにして下さい", message: "「設定」アプリ ⇒「プライバシー」⇒「位置情報サービス」からオンにできます")
        }
    }
```

こんな感じの実装となり、どちらも記述します。
メソッド内は、バージョンの指定以外、ほぼ変わらないですね。

これで`iOS14以前のバージョン`に対応できました。

#エラー処理
どの場面でも大体、エラー処理というものは存在しますが、もちろんCLLocationManagerでも存在します。
[func locationManager(_ manager: CLLocationManager, didFailWithError error: Error)](https://developer.apple.com/documentation/corelocation/cllocationmanagerdelegate/1423786-locationmanager)

では、どのようにViewController内で記述するのかというと、こんな感じです↓

```swift:ViewController
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
    //本来なら、アラートで画面にエラー内容を表示したりする
    print("\((error as NSError).domain)")
}
```

このデリゲートメソッドは`CLLocationManagerが位置情報を取得できなかった時`に呼ばれます。
このメソッドを実装していない場合、エラーをスルーしてしまうので`要注意`です。

様々なエラーがあるので、見てみて下さい↓
[CLError](https://developer.apple.com/documentation/corelocation/clerror)


そして、ここからは自分が苦戦した所を紹介します。

#苦戦した部分~認証ステータスの取得~
認証ステータスの取得も、先ほど紹介しましたが`推奨・非推奨のコード`がありましたね。
ここが、かなり厄介？で現在進行形で悩んでいます...。

```swift
// 位置情報の認証ステータス取得

// 推奨していない iOS 4.2–14.0
func authorizationStatus() -> CLAuthorizationStatus

// 推奨している iOS14-
var authorizationStatus: CLAuthorizationStatus
```

まず、この2つのコードを比較すると
推奨していない方は`メソッド`、推奨している方は`プロパティ`となっています。

ここがまず、大きな違いですね。

そして、推奨していない`func authorizationStatus()`メソッドは
アプリが`バックグラウンド・フォアグラウンド状態`関係なく認証ステータスを返してくれますが

今回、iOS14で新しく登場した`authorizationStatus`プロパティの方は
`フォアグラウンド状態`の時の認証ステータスしか返してくれません。

つまり、`authorizationStatus`プロパティの方は`バックグラウンド状態`で認証ステータスを変更した場合
`フォアグラウンド状態`の時の認証ステータスを返すんですね。

じゃあ、この部分の一体どこで苦戦したのかというと

`バックグラウンド状態`から戻ってきた時に`アプリの位置情報サービスの有無`をチェックする部分です。

実際に分かりやすいようにコードで説明しますね。
`認証ステータス`を取得する場合、今までだと下記の実装でした↓

```swift:ViewController
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
    }

   // バックグラウンド状態から戻った時に呼ばれるメソッド
   @objc private func willEnterForeground() {
      if CLLocationManager.locationServicesEnabled() {
         // 認証ステータスの取得(非推奨)
         let status = CLLocationManager.authorizationStatus()

         switch status {
         
         // 許可しない場合
         case .denied:
             Alert.okAlert(vc: self, title: "アプリの位置情報サービスを\nオンにして下さい", message: "OKをタップすると設定アプリに移動します") { (_) in
                 UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
             }
                
            default:
                break
            }

        }else {
            Alert.okAlert(vc: self, title: "位置情報サービスを\nオンにして下さい", message: "「設定」アプリ ⇒「プライバシー」⇒「位置情報サービス」からオンにできます")
        }
      }
   }
```

では次に、`推奨`しているコードでの実装↓

```swift:ViewController
    private var locationManager: CLLocationManager = {
        var locationManager = CLLocationManager()
        locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
        locationManager.distanceFilter = 5
        return locationManager
    }()

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
    }

   // バックグラウンド状態から戻った時に呼ばれるメソッド
   @objc private func willEnterForeground() {
      if CLLocationManager.locationServicesEnabled() {
         // 認証ステータスの取得(推奨)
         let status = locationManager.authorizationStatus

         switch status {
         
         // 許可しない場合
         case .denied:
             Alert.okAlert(vc: self, title: "アプリの位置情報サービスを\nオンにして下さい", message: "OKをタップすると設定アプリに移動します") { (_) in
                 UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
             }
                
            default:
                break
            }

        }else {
            Alert.okAlert(vc: self, title: "位置情報サービスを\nオンにして下さい", message: "「設定」アプリ ⇒「プライバシー」⇒「位置情報サービス」からオンにできます")
        }
      }
   }
```
さほど変化はないんですが、これをビルドしてみると違いが分かります。

まず、どちらも`起動時`に認証ステータスが `.denid`である場合はちゃんとアラートを表示します。
ですが、`バックグラウンド状態`で .denid > .restrictedに変更して`フォアグラウンド状態`に戻すとこうなります↓
![ezgif.com-gif-maker4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743730/d102cf05-f26e-26e3-63ae-fca87b1e3960.gif)

本来、`許可しない(.denid)`で表示されるアラートが
`Appの使用中は許可(.authorizedWhenInUse)`でも表示されてしまいました。

この不具合が、発生してしまうので仕方なく非推奨の`func authorizationStatus()`メソッド
を呼んでいる現状なんですね。

#authorizationStatusプロパティはどう使うんだろう?
色々、試行錯誤していきながら調べながら自分はこういう結論に至りました。

そもそも、iOS14で登場した`authorizationStatus`プロパティは

`func locationManagerDidChangeAuthorization(_ manager: CLLocationManager)`ありきで使用してこのメソッド内で使っていく。

じゃあ、そもそも非推奨の`func authorizationStatus()`メソッドは使わなくても良いんじゃない？

そのメソッドは`CLLocationManagerクラスの初期化及び、バックグラウンド・フォアグラウンド状態関係なく認証ステータスが変更されたら呼ばれる`デリゲートメソッドだし、`willEnterForeground()`は必要ないのでは？↓

```swift
// バックグラウンド時に戻った時に呼ばれるメソッド
@objc private func willEnterForeground()
```

と思う方がいらっしゃると思います。

では、このような場面ではどうでしょうか？

例えば、ユーザーに対して位置情報の許可をリクエストして
アラートが表示され、`許可しない`を選択したとします。
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743730/8e4b4a91-a10b-cfcb-aed9-2fe313d899ee.jpeg">

今までの流れからいくと、このようなアラートが表示されて`設定アプリ`に画面遷移します。
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743730/a7056a05-bc82-ffff-b485-98cf7c114c24.jpeg">

これで、設定アプリに画面遷移したはいいけど`何もしないで戻った時`に
`func willEnterForeground()`を呼ばない場合どうでしょうか？

呼ばない場合は、何もチェックされません。

もちろん、認証ステータスが変更されていないので
`func locationManagerDidChangeAuthorization(_ manager: CLLocationManager)`
も呼ばれません。

以上の理由から`func willEnterForeground()`を使用していますし、
非推奨の`func authorizationStatus()`メソッドも使用している現状なんですね。

この部分の改善方法を色々、調べたんですが最新の記事でも非推奨の`func authorizationStatus()`メソッドを使用していて頭の中は???でいっぱいでした。

この解決方法をご存知の方は、コメントで教えて下さると有り難いです。

#おわり
という事で三部構成という長い記事になってしまいましたが、今回で完結です。
位置情報を取得するだけで、ここまで配慮しなくてはいけないというのはびっくりですね。

もしかしたら、ここは全然違うよー！という部分があるかもしれません。
そういう場合は遠慮なく、コメントして下さると有り難いです。

※おわりで言うのもなんですが、アラートのタイトルなどで何度も同じことを書くのは
`マジックナンバー`扱いになってしまい駄目なのですが、これは後日、記事としてまとめて投稿します。

ここまでのソースコードは下記にて載せておきますので良かったら参考にしてみて下さい↓

最後まで読んで下さり、ありがとうございました。

#※追記
`extentionファイル`を作って拡張すれば、ViewControllerのボリュームを抑えることも出来ます↓

```swift:CLLocationManager+
import UIKit
import CoreLocation

extension CLLocationManager {
    func alertStatusIfNeeded(vc: UIViewController) {
        if #available(iOS 14.0, *) {
            if CLLocationManager.locationServicesEnabled() {
                let status = self.authorizationStatus
                
                switch status {
                case .notDetermined:
                    self.requestWhenInUseAuthorization()
                    
                case .restricted:
                    // アラート表示したり
                    
                case .denied:
                    // アラート表示したり
                    
                case .authorizedAlways:
                    self.startUpdatingLocation()
                    
                case .authorizedWhenInUse:
                    self.startUpdatingLocation()
                    
                default:
                    break
                }
            }else {
                // アラート表示したり
            }
        }
    }
    
    func alertStatusIfNeededUnderiOSVer(vc: UIViewController) {
        if CLLocationManager.locationServicesEnabled() {
            let status = CLLocationManager.authorizationStatus()
            
            switch status {
            case .notDetermined:
                self.requestWhenInUseAuthorization()
                
            case .restricted:
                // アラート表示したり
                
            case .denied:
                // アラート表示したり
                
            case .authorizedAlways:
                self.startUpdatingLocation()
                
            case .authorizedWhenInUse:
                self.startUpdatingLocation()
                
            default:
                break
            }
        }else {
           // アラート表示したり
    }
    
    func alertStatusIfNeededBackground(vc: UIViewController) {
        if !CLLocationManager.locationServicesEnabled() {
            // アラート表示したり
            return
        }

        let status = CLLocationManager.authorizationStatus()
        
        if status == .denied {
            // アラート表示したり
        }
    }
}
```
ViewControllerではこんな感じになります↓
※一部抜粋

```swift:ViewController
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        manager.alertStatusIfNeeded(vc: self)
    }
```

#ソースコード
[こちら](https://github.com/reo0612/sample-location)にもあげてます。

```swift:ViewController
import UIKit
import CoreLocation

final class ViewController: UIViewController {

    private var locationManager: CLLocationManager = {
        var locationManager = CLLocationManager()
        locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
        locationManager.distanceFilter = 5
        return locationManager
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        locationManager.requestWhenInUseAuthorization()
        locationManager.delegate = self
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
    }
    
    @objc private func willEnterForeground() {
        if CLLocationManager.locationServicesEnabled() {
            
            let status = CLLocationManager.authorizationStatus()
            
            switch status {
            case .denied:
                Alert.okAlert(vc: self, title: "アプリの位置情報サービスを\nオンにして下さい", message: "OKをタップすると設定アプリに移動します") { (_) in
                    UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
                }
                
            default:
                break
            }
            
        }else {
            Alert.okAlert(vc: self, title: "位置情報サービスを\nオンにして下さい", message: "「設定」アプリ ⇒「プライバシー」⇒「位置情報サービス」からオンにできます")
        }
    }
}

extension ViewController: CLLocationManagerDelegate {
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        if #available(iOS 14.0, *) {
            if CLLocationManager.locationServicesEnabled() {
                
                let status = manager.authorizationStatus
                
                switch status {
                case .authorizedAlways, .authorizedWhenInUse:
                    manager.startUpdatingLocation()
                    
                case .notDetermined:
                    manager.requestWhenInUseAuthorization()
                    
                case .denied:
                    Alert.okAlert(vc: self, title: "アプリの位置情報サービスを\nオンにして下さい", message: "OKをタップすると設定アプリに移動します") { (_) in
                        UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
                    }
                    
                case .restricted:
                    Alert.okAlert(vc: self, title: "位置情報サービスの使用を\n許可されていません", message: "")
                    
                default:
                    break
                }
            }else {
                Alert.okAlert(vc: self, title: "位置情報サービスを\nオンにして下さい", message: "「設定」アプリ ⇒「プライバシー」⇒「位置情報サービス」からオンにできます")
            }
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        if CLLocationManager.locationServicesEnabled() {
            
            switch status {
            case .authorizedAlways, .authorizedWhenInUse:
                manager.startUpdatingLocation()
                
            case .notDetermined:
                manager.requestWhenInUseAuthorization()
                
            case .denied:
                Alert.okAlert(vc: self, title: "アプリの位置情報サービスを\nオンにして下さい", message: "OKをタップすると設定アプリに移動します") { (_) in
                    UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
                }
                
            case .restricted:
                Alert.okAlert(vc: self, title: "位置情報サービスの使用を\n許可されていません", message: "")
                
            default:
                break
            }
            
        }else {
            Alert.okAlert(vc: self, title: "位置情報サービスを\nオンにして下さい", message: "「設定」アプリ ⇒「プライバシー」⇒「位置情報サービス」からオンにできます")
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let gps = manager.location?.coordinate else {
            return
        }
        manager.stopUpdatingLocation()
        let lat = gps.latitude
        let lng = gps.longitude
        print("経度：\(String(describing: lat)), 緯度：\(String(describing: lng))")
    }
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print("\((error as NSError).domain)")
    }
}
```

```swift:Alert
import UIKit

final class Alert {
    static func okAlert(vc: UIViewController,title: String, message: String, handler: ((UIAlertAction) -> Void)? = nil) {
        let okAlertVC = UIAlertController(title: title, message: message, preferredStyle: .alert)
        okAlertVC.addAction(UIAlertAction(title: "OK", style: .default, handler: handler))
        vc.present(okAlertVC, animated: true, completion: nil)
    }
}
```