【iOS】位置情報を取得するCLLocationManagerの基本知識

# 位置情報の取得

[iOS14で位置情報を取得する](https://qiita.com/r0227n/items/6ae818adaab029436bf5)
[iOSアプリで位置情報を取得するときに配慮する点をまとめてみた①](https://qiita.com/reo0612/items/589ea45a0ce2cbd6ec07)
[Adding Location Services to Your App](https://developer.apple.com/documentation/corelocation/adding_location_services_to_your_app)

## デバイスが対応しているかを確認

`位置情報サービス`をアプリで利用する場合は、アプリを使用するデバイスが対応しているか確認する必要がある。

|メソッド|概要|
|---|---|
|[significantLocationChangeMonitoringAvailable()](https://developer.apple.com/documentation/corelocation/cllocationmanager/1423677-significantlocationchangemonitor)|位置情報が大きく変化した場合に、位置情報が更新できるかどうか|
|[isMonitoringAvailable(for:)](https://developer.apple.com/documentation/corelocation/cllocationmanager/1423654-ismonitoringavailable)|現在地が位置情報を取得できる地点かどうか|
|[headingAvailable()](https://developer.apple.com/documentation/corelocation/cllocationmanager/1423502-headingavailable)|進行方向を追跡できるかどうか|
|[isRangingAvailable()](https://developer.apple.com/documentation/corelocation/cllocationmanager/1620549-israngingavailable)|[iBeacon](https://developer.apple.com/ibeacon/)に対応しているかどうか|

## 位置情報サービスが利用できない場合のエラーハンドリング

また、デバイスで位置情報サービスが利用できない場合に送出されるエラーをハンドリングするため、
`CLLocationManagerDelegate`で定義された[locationManager(_:didFailWithError:)](https://developer.apple.com/documentation/corelocation/cllocationmanagerdelegate/1423786-locationmanager)を実装するのが望ましい。

## ユーザに位置情報サービスの認証を求める

[Choosing the Location Services Authorization to Request](https://developer.apple.com/documentation/corelocation/choosing_the_location_services_authorization_to_request)

位置情報サービスの認証状態は**アプリの使用中のみ**と**常時**の2つがあり、それぞれ以下のタイミングで位置情報サービスの利用が可能となる。

|認証状態|利用可能タイミング|
|---|---|
|アプリの使用中のみ|**フォアグラウンドで実行中**の場合<br>**バックグラウンドで実行中** かつ **位置情報インジケータが有効**である場合|
|常時|-|

ただし、プライバシーや電力消費量などの点で、位置情報サービスの利用はなるべく**アプリの使用中のみ**に限定するのが望ましい。

## 位置情報サービスの利用とイベントの受信

|クラス・プロトコル|メソッド|概要|
|---|---|---|
|CLLocationManager|[startUpdatingLocation()](https://developer.apple.com/documentation/corelocation/cllocationmanager/1423750-startupdatinglocation)|位置情報サービスの開始|
|CLLocationManagerDelegate|[locationManagerDidChangeAuthorization(_:)](https://developer.apple.com/documentation/corelocation/cllocationmanagerdelegate/3563956-locationmanagerdidchangeauthoriz)|位置情報サービスの認証状態が変化|
|CLLocationManagerDelegate|[locationManager(_:didUpdateLocations:)](https://developer.apple.com/documentation/corelocation/cllocationmanagerdelegate/1423615-locationmanager)|位置情報が変化|
|CLLocationManagerDelegate|[locationManager(_:didUpdateHeading:)](https://developer.apple.com/documentation/corelocation/cllocationmanagerdelegate/1621555-locationmanager)|進行方向が変化|
|CLLocationManager|[requestLocation()](https://developer.apple.com/documentation/corelocation/cllocationmanager/1620548-requestlocation)|現在地の取得|
|CLLocationManager|[stopUpdatingLocation()](https://developer.apple.com/documentation/corelocation/cllocationmanager/1423695-stopupdatinglocation)|位置情報サービスの終了|

#### CLLocationManager

[CLLocationManager](https://developer.apple.com/documentation/corelocation/cllocationmanager)

**位置情報**を扱う`Core Location`サービスの開始と停止・設定を行う場合は[CLLocationManager](https://developer.apple.com/documentation/corelocation/cllocationmanager)を利用する。

`CLLocationManager`は非同期で動作する`Task`を実行するため、ローカル変数ではなく**強参照のプロパティ**としてアプリ内に保管しておく必要がある。

また、`CLLocationManager`の初期化時に`CLLocationManagerDelegate`で抽象的に定義された[locationManagerDidChangeAuthorization(_:)](https://developer.apple.com/documentation/corelocation/cllocationmanagerdelegate/3563956-locationmanagerdidchangeauthoriz)メソッドを呼び出すため、
`CLLocationManagerDelegate`プロトコルの実装クラスに対して、`CLLocationManager`インスタンスの`delegate`プロパティをセットする必要がある。

```swift
class ViewController: UIViewController {
  // CLLocationManagerは非同期処理を行うため、強参照のプロパティとしてアプリ内で保管させる
  var locationManager: CLLocationManager?
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    locationManager = CLLocationManager()
    
    // CLLocationManagerがCLLocationManagerDelegateプロトコルの抽象メソッドを実行するときは、
    // CLLocationManagerDelegateプロトコルの実装クラスであるViewControllerに実行してもらう
    locationManager!.delegate = self
    
    // アプリの使用中のみ位置情報サービスの利用許可を求める
    locationManager!.requestWhenInUseAuthorization()
  }
}

// Delegateプロトコルの抽象メソッドを実装
// → ここで具体的な処理が実装される
extension ViewController: CLLocationManagerDelegate {
  func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
    // 位置情報サービスの認証情報が変更された場合に実行する処理
  }
}
```

## 位置情報の取得に関する設定

[[iOS14]WWDC 2020 Core Location 新要素 Preciseについて](https://qiita.com/satoru_pripara/items/7dbaf59dc840d679751c)
[位置情報を正確にトラッキングする技術 in iOS — (第３回) バックグランドでのトラッキング、精度、バッテリー消費](https://medium.com/location-tracking-tech/%E4%BD%8D%E7%BD%AE%E6%83%85%E5%A0%B1%E3%82%92%E6%AD%A3%E7%A2%BA%E3%81%AB%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0%E3%81%99%E3%82%8B%E6%8A%80%E8%A1%93-in-ios-%E7%AC%AC%EF%BC%93%E5%9B%9E-%E3%83%90%E3%83%83%E3%82%AF%E3%82%B0%E3%83%A9%E3%83%B3%E3%83%89%E3%81%AE%E3%81%A7%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0-%E7%B2%BE%E5%BA%A6-%E3%83%90%E3%83%83%E3%83%86%E3%83%AA%E3%83%BC%E6%B6%88%E8%B2%BB-a49c3cdb4a5a)

`CLLocationManager`の[startUpdatingLocation()](https://developer.apple.com/documentation/corelocation/cllocationmanager/1423750-startupdatinglocation)や[requestLocation()](https://developer.apple.com/documentation/corelocation/cllocationmanager/1620548-requestlocation)で位置情報を取得する前に、
`CLLocationManager`の位置情報の取得タイミングや取得精度などを定義する各プロパティに値をセットする。

|プロパティ|型|概要|
|---|---|---|
|[accuracyAuthorization](https://developer.apple.com/documentation/corelocation/cllocationmanager/3600215-accuracyauthorization)|[CLAccuracyAuthorization](https://developer.apple.com/documentation/corelocation/claccuracyauthorization)|**ユーザの承認によって決まる**位置情報の取得精度|
|[pausesLocationUpdatesAutomatically](https://developer.apple.com/documentation/corelocation/cllocationmanager/1620553-pauseslocationupdatesautomatical)|Bool|位置情報が変化していない場合(※`activityType`を参照)に、位置情報の更新を一時停止するかどうか|
|[allowsBackgroundLocationUpdates](https://developer.apple.com/documentation/corelocation/cllocationmanager/1620568-allowsbackgroundlocationupdates)|Bool|バックグラウンドでの位置情報の更新を行うかどうか|
|[showsBackgroundLocationIndicator](https://developer.apple.com/documentation/corelocation/cllocationmanager/2923541-showsbackgroundlocationindicator)|Bool|バックグラウンドで位置情報の取得を行っている場合に、インジケータを表示するかどうか|
|[activityType](https://developer.apple.com/documentation/corelocation/cllocationmanager/1620567-activitytype)|[CLActivityType](https://developer.apple.com/documentation/corelocation/clactivitytype)|位置情報が変化しているかどうかを識別するためのユーザのアクティビティ情報|
|[distanceFilter](https://developer.apple.com/documentation/corelocation/cllocationmanager/1423500-distancefilter)|[CLLocationDistance](https://developer.apple.com/documentation/corelocation/cllocationdistance)|位置情報の取得間隔(距離)|
|[desiredAccuracy](https://developer.apple.com/documentation/corelocation/cllocationmanager/1423836-desiredaccuracy)|[CLLocationAccuracy](https://developer.apple.com/documentation/corelocation/cllocationaccuracy)|位置情報の取得精度|
|[headingFilter](https://developer.apple.com/documentation/corelocation/cllocationmanager/1620550-headingfilter)|[CLLocationDegrees](https://developer.apple.com/documentation/corelocation/cllocationdegrees)|進行方向の取得間隔(角度)|
|[headingOrientation](https://developer.apple.com/documentation/corelocation/cllocationmanager/1620556-headingorientation)|[CLDeviceOrientation](https://developer.apple.com/documentation/corelocation/cldeviceorientation)|進行方向を決定する際の画面の向き|

#### Delegateパターン

[【Swift入門】難解なデリゲート(delegate)の使い方を理解しよう！](https://www.sejuku.net/blog/33867)

```swift
// Delegateプロトコル … 担当範囲のメソッドを抽象的に定義
protocol AttackDelegate {
  // 攻撃するだけ(防御や回避は担当範囲外)
  func attack()
}

// 依頼者 … 戦闘を行うが、どんな攻撃をするかは未定
class Fighter {
  // 攻撃方法は後から自由に変更可能
  var atkDelegate: AttackDelegate?
  
  // 戦う
  func fight() {
    // 攻撃方法が決まっている場合は攻撃
    if let delegate = self.delegate {
      delegate.attack()
    }
  }
}

// 実行者 … 攻撃方法を決める(攻撃専門で、防御や回避は担当範囲外)
class Boxer: AttackDelegate {
  // 攻撃方法を決める
  func attack() {
    print("パンチ")
  }
}

// ↓↓↓

let fighter = Fighter()       // Fighterをインスタンス化
let boxer = Boxer()           // Boxerをインスタンス化
fighter.atkDelegate = boxer   // 攻撃をBoxerに委譲
fighter.fight()               // Boxerにパンチしてもらう
```