[swift5]iOSアプリで位置情報取得機能を実装

現在いる位置情報をラベルに反映するアプリの実装方法を紹介します！

ここでは、"常に位置情報を取得しない"実装にします。
"常に位置情報を取得する"ものについては別途記載します。（作成が完了したらここにリンクを追加します・・・）

##アプリの概要

### デフォルト

緯度と経度を表示するラベルに「デフォルト」と表示されています。

|![default_location.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547575/f82b2318-1590-4fdc-10fc-10b3fa87d9bf.png)|
|:-:|

### 位置情報を取得ボタン押下時

緯度と経度を表示するラベルに、位置情報が表示されます。
（画像は、シュミレータにて位置情報を東京に設定しています。）

|![tokyo_location.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547575/bee40c9b-b294-69f1-3d4a-eef6259414f3.png)
|:-:|

![スクリーンショット 2019-12-08 19.31.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547575/f2c59903-5ac2-471f-4447-517c09210136.png)

### クリアボタン押下時

緯度と経度を表示するラベルに「デフォルト」と表示されます。

|![clear_location.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547575/f82b2318-1590-4fdc-10fc-10b3fa87d9bf.png)|
|:-:|


## 動作環境

| 対象 | バージョン |
|---|---|
| iOS | 13.3 |
| macOS | Catalina 10.15.2 |
| Xcode | 11.3.1 |
| Swift | 5.1.3 |

## 現在地を表示

### Info.plistの修正

Info.plist に、「Privacy - Location When In Use Usage Discription」を追加してください。

###ファイル作成

- Storyboard
    - ここでは、"Location.storyboard" とします
- ViewController.swift
    - ここでは、"LocationViewController.swift" とします

### 画面を作成

ラベル4つとボタン2つを配置します。

|![default_location.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547575/f82b2318-1590-4fdc-10fc-10b3fa87d9bf.png)|
|:-:|

### 画面とソースの紐付け

ラベル2つとボタン2つをソースコード上に紐付けします。

```swift
/// 緯度を表示するラベル
@IBOutlet weak var latitude: UILabel!
/// 経度を表示するラベル
@IBOutlet weak var longitude: UILabel!

/// "位置情報を取得"ボタンを押下した際、位置情報をラベルに反映する
/// - Parameter sender: "位置情報を取得"ボタン
@IBAction func getLocationInfo(_ sender: Any) {
}

/// "クリア"ボタンを押下した際、ラベルを初期化する
/// - Parameter sender: "クリア"ボタン
@IBAction func clearLabel(_ sender: Any) {
}
```

### Core Location のインポート

- Core Location については、[Core Location（公式）](https://developer.apple.com/documentation/corelocation/)を参照してください。

```swift
import CoreLocation
```

### マネージャの初期化

はじめに、フィールド変数として locationManager を定義します。

```swift
/// ロケーションマネージャ
var locationManager: CLLocationManager!
```

次に、ロケーションマネージャをセットアップするメソッドを実装します。

```swift
/// ロケーションマネージャのセットアップ
func setupLocationManager() {
    locationManager = CLLocationManager()
}
```

また、このメソッドを viewDidLoad で呼び出します。

```swift
override func viewDidLoad() {
    super.viewDidLoad()

    // ロケーションマネージャのセットアップ
    setupLocationManager()
}
```

### 位置情報取得許可ダイアログの表示

位置情報取得許可ダイアログを表示する処理を実装します。

![dialog.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547575/99a5461e-30f8-2344-b3bb-b5d2dbaf0041.png)

ロケーションマネージャのセットアップするメソッド（本ページだと、setupLocationManager メソッド）の最後尾に以下の処理を実装します。

```swift
// 位置情報取得許可ダイアログの表示
guard let locationManager = locationManager else { return }
locationManager.requestWhenInUseAuthorization()
```

### マネージャの設定

マネージャの設定を行います。
また、ユーザが「App使用中は許可」または「1度だけ許可」の許可を押下した場合のみ、位置情報取得を開始します。

ロケーションマネージャのセットアップするメソッド（本ページだと、setupLocationManager メソッド）の最後尾に以下の処理を実装します。

```swift
// マネージャの設定
let status = CLLocationManager.authorizationStatus()
// ステータスごとの処理
if status == .authorizedWhenInUse {
    locationManager.delegate = self
    // 位置情報取得を開始
    locationManager.startUpdatingLocation()
}
```

### 位置情報が更新されるたびに位置情報を取得

位置情報が更新されるたびに位置情報を取得するため、CLLocationManagerDelegate プロトコルを採用します。

はじめに、位置情報を格納する用のフィールド変数を実装します。

```swift
// 緯度
var latitudeNow: String = ""
// 経度
var longitudeNow: String = ""
```

次に、setupLocationManager()メソッド内で、ユーザが「App使用中は許可」または「1度だけ許可」の許可を押下した場合のみ locationViewController が管理マネージャのデリゲート先になるようにします。

```swift
// ステータスごとの処理
if status == .authorizedWhenInUse {
    locationManager.delegate = self // これを追加
    // 位置情報取得を開始
    locationManager.startUpdatingLocation()
}
```

次に、CLLocationManagerDelegate 用の extention を実装します。
また、その中に locationManager(_:didUpdateLocations:) メソッドを実装します。

- locationManager(_:didUpdateLocations:) メソッドは、位置情報が更新されるたびに呼び出されるメソッドです。
   - 詳しくは、[locationManager(_:didUpdateLocations:)（公式）](https://developer.apple.com/documentation/corelocation/cllocationmanagerdelegate/1423615-locationmanager)を参照してください。


```swift
extension LocationViewController: CLLocationManagerDelegate {
    /// 位置情報が更新された際、位置情報を格納する
    /// - Parameters:
    ///   - manager: ロケーションマネージャ
    ///   - locations: 位置情報
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    }
}
```

最後に、位置情報を取得するたびに位置情報を格納する用のフィールド変数にセットします。

```swift
extension LocationViewController: CLLocationManagerDelegate {

    /// 位置情報が更新された際、位置情報を格納する
    /// - Parameters:
    ///   - manager: ロケーションマネージャ
    ///   - locations: 位置情報
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        let location = locations.first
        let latitude = location?.coordinate.latitude
        let longitude = location?.coordinate.longitude
        // 位置情報を格納する
        self.latitudeNow = String(latitude!)
        self.longitudeNow = String(longitude!)
    }
}
```

### ボタンを押下するたびに承認ステータスごとの処理

```swift
/// "位置情報を取得"ボタンを押下した際、位置情報をラベルに反映する
/// - Parameter sender: "位置情報を取得"ボタン
@IBAction func getLocationInfo(_ sender: Any) {
    // マネージャの設定
    let status = CLLocationManager.authorizationStatus()
    if status == .denied {
        showAlert()
    } else if status == .authorizedWhenInUse {
        self.latitude.text = latitudeNow
        self.longitude.text = longitudeNow
    }
}
```

```swift
/// アラートを表示する
func showAlert() {
    let alertTitle = "位置情報取得が許可されていません。"
    let alertMessage = "設定アプリの「プライバシー > 位置情報サービス」から変更してください。"
    let alert: UIAlertController = UIAlertController(
        title: alertTitle,
        message: alertMessage,
        preferredStyle:  UIAlertController.Style.alert
    )
    // OKボタン
    let defaultAction: UIAlertAction = UIAlertAction(
        title: "OK",
        style: UIAlertAction.Style.default,
        handler: nil
    )
    // UIAlertController に Action を追加
    alert.addAction(defaultAction)
    // Alertを表示
    present(alert, animated: true, completion: nil)
}
```

### ボタンを押下するたびにラベルを初期化

```swift
/// "クリア"ボタンを押下した際、ラベルを初期化する
/// - Parameter sender: "クリア"ボタン
@IBAction func clearLabel(_ sender: Any) {
    self.latitude.text = "デフォルト"
    self.longitude.text = "デフォルト"
}
```

## ソースコード

```swift

import UIKit
import CoreLocation

/// Location の View
class LocationViewController: UIViewController {

    /// 緯度を表示するラベル
    @IBOutlet weak var latitude: UILabel!
    /// 経度を表示するラベル
    @IBOutlet weak var longitude: UILabel!

    // 緯度
    var latitudeNow: String = ""
    // 経度
    var longitudeNow: String = ""

    /// ロケーションマネージャ
    var locationManager: CLLocationManager!

    override func viewDidLoad() {
        super.viewDidLoad()

        // ロケーションマネージャのセットアップ
        setupLocationManager()
    }

    /// "位置情報を取得"ボタンを押下した際、位置情報をラベルに反映する
    /// - Parameter sender: "位置情報を取得"ボタン
    @IBAction func getLocationInfo(_ sender: Any) {
        // マネージャの設定
        let status = CLLocationManager.authorizationStatus()
        if status == .denied {
            showAlert()
        } else if status == .authorizedWhenInUse {
            self.latitude.text = latitudeNow
            self.longitude.text = longitudeNow
        }
    }

    /// "クリア"ボタンを押下した際、ラベルを初期化する
    /// - Parameter sender: "クリア"ボタン
    @IBAction func clearLabel(_ sender: Any) {
        self.latitude.text = "デフォルト"
        self.longitude.text = "デフォルト"
    }


    /// ロケーションマネージャのセットアップ
    func setupLocationManager() {
        locationManager = CLLocationManager()

        // 権限をリクエスト
        guard let locationManager = locationManager else { return }
        locationManager.requestWhenInUseAuthorization()

        // マネージャの設定
        let status = CLLocationManager.authorizationStatus()

        // ステータスごとの処理
        if status == .authorizedWhenInUse {
            locationManager.delegate = self
            locationManager.startUpdatingLocation()
        }
    }

    /// アラートを表示する
    func showAlert() {
        let alertTitle = "位置情報取得が許可されていません。"
        let alertMessage = "設定アプリの「プライバシー > 位置情報サービス」から変更してください。"
        let alert: UIAlertController = UIAlertController(
            title: alertTitle,
            message: alertMessage,
            preferredStyle:  UIAlertController.Style.alert
        )
        // OKボタン
        let defaultAction: UIAlertAction = UIAlertAction(
            title: "OK",
            style: UIAlertAction.Style.default,
            handler: nil
        )
        // UIAlertController に Action を追加
        alert.addAction(defaultAction)
        // Alertを表示
        present(alert, animated: true, completion: nil)
    }
}

extension LocationViewController: CLLocationManagerDelegate {

    /// 位置情報が更新された際、位置情報を格納する
    /// - Parameters:
    ///   - manager: ロケーションマネージャ
    ///   - locations: 位置情報
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        let location = locations.first
        let latitude = location?.coordinate.latitude
        let longitude = location?.coordinate.longitude
        // 位置情報を格納する
        self.latitudeNow = String(latitude!)
        self.longitudeNow = String(longitude!)
    }
}
```