iOSアプリで位置情報を取得してマップ上に表示する

## はじめに

前から位置情報やマップを表示するアプリを作ってみたいと思ってたので作ってみた。

### 動作環境

- Xcode 10.1
- Swift 4.2.1

### 位置情報を使用する上で知っておくこと

#### 位置情報に関する要素

- Core Location（デバイスの地理的位置と向きを取得する）

#### 地図表示に関する要素

- MapKit（地図を表示する）

#### 認証リクエスト

- iOSアプリでは、ユーザーの位置や連絡先などプライバシーに関わる情報を取得する前にユーザーから許可を得る必要がある。（認証リクエストAPIを呼び出す必要がある）
- 認証リクエストAPIには使用用途に応じて次の２種類がある
  - 起動時のみ: `requestWhenInUseAuthorization`
  - 常時: `requestAlwaysAuthorization`
- **requestWhenInUseAuthorization** アプリまたはその機能が画面上に表示されている時だけ、位置情報の利用を許可するもので、アプリがバックグラウンド状態の時は、許可されない。
- **requestAlwaysAuthorization** バックグラウンドで実行中の場合にも位置情報の利用を許可するものです。

この認証APIは、何度でも呼び出すことができますが、ダイアログが表示されるのはステータスが **NotDetermined（未設定）** の時だけです。 したがって、一度ダイアログが表示されてユーザが「許可する/しない」の選択をした後は、このメソッドを呼び出しても何も起こりません。

#### 認証の種類

- **NotDetermined**: **まだ設定していない**
- **restricted**: **「設定」⇒「一般」⇒「機能制限」で、位置情報サービスの利用が制限されている**
- **denied**: **「許可しない」に設定されている**
- **authorizedAlways**: **「常に許可」に設定されている。**
- **authorizedWhenInUse**: **「このAppの使用中のみ許可」に設定されている。**

#### 利用目的の記載

認証リクエストを呼び出すためには、「位置情報を利用する目的」を `Info.plist`に記載する必要がある。

**requestWhenInUseAuthorization** → **Privacy - Location When In Use Usage Description**

**requestAlwaysAuthorization** → **Privacy - Location Always and When In Use Usage Description**

ダイアログでユーザが選択した内容は、アプリの設定情報として記憶され、これをアプリ側から変更することは出来ない。

## マップをViewに表示する

### 手順

1. プロジェクト新規作成
2. フレームワーク導入
3. ViewControllerにMapKitとCoreLocationをimportし、main.storyboard にMapViewを配置する
4. 実行

プロジェクト作成からビルドしてシミュレーターでマップを表示させるところまでをまずはやっていく

### 1. プロジェクト新規作成

XcodeのCreate a New Xcode Project から Single View Applicationを選択します。

Product Nameは **MapApp**　(任意)　
Languageは **Swift**
Devicesは **iPhone** 
をそれぞれ選択します。

### 2. フレーワーク導入

**TARGETS** > **Build Phases** > **Link Binary With Libraries** から **MapKit.framework**, **Core Location.framework** を追加します

<img width="869" alt="スクリーンショット 2019-07-20 11.14.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16044/2e5f396d-e289-ce47-23e9-c51ec4884e48.png">

### 3.  ViewControllerにMapKitとCoreLocationをimportし、main.storyboard にMapViewを配置する

```ViewController.swift
import UIKit
import MapKit
import CoreLocation

class ViewController: UIViewController {

    @IBOutlet weak var mapView: MKMapView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
}

```

### 4. 実行

これで実行すればこんな感じ！！！

<img width="300" alt="681d7d27-a512-1c85-2848-db4c41d7a47c.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16044/e6cfccac-3854-87aa-c760-1181abf204c5.png">



## 認証をリクエストし、自分の位置情報と目的地のピンをMap上に表示する

```ViewController.swift
import UIKit
import MapKit
import CoreLocation

class ViewController: UIViewController {

    @IBOutlet weak var mapView: MKMapView!

    var locationManager: CLLocationManager!

    fileprivate func initializeLocationManager() {
        locationManager = CLLocationManager()
    }
    
    fileprivate func requestAuthorization() {
        locationManager.requestAlwaysAuthorization()
    }
    
    fileprivate func setupLocationManagerIfNeeded() {
        let status = CLLocationManager.authorizationStatus()
        if status == .authorizedWhenInUse || status == .authorizedAlways {
            locationManager.delegate = self
            locationManager.distanceFilter = 10
            locationManager.startUpdatingLocation()
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        initializeLocationManager()
        requestAuthorization()
        setupLocationManagerIfNeeded()
    }
}

extension ViewController: CLLocationManagerDelegate {
    // 位置情報取得に失敗したときに呼び出される
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error.localizedDescription)
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        // 取得した位置情報の緯度経度
        let location = locations.first
        let latitude = location?.coordinate.latitude ?? 0
        let longitude = location?.coordinate.longitude ?? 0
        let locationCoordinate = CLLocationCoordinate2DMake(latitude, longitude)
        
        // 表示するマップの中心を、取得した位置情報のポイントに指定
        mapView.setCenter(locationCoordinate, animated: true)
        
        var region = mapView.region

        region.center = locationCoordinate
        region.span.latitudeDelta = 0.02
        region.span.longitudeDelta = 0.02

        mapView.setRegion(region, animated: true)

        let annotation = MKPointAnnotation()

        annotation.coordinate = CLLocationCoordinate2DMake(35.696135, 139.768322)
        annotation.title = "目的地"

        self.mapView.addAnnotation(annotation)

        // 位置情報取得を停止
        locationManager.stopUpdatingLocation()
    }
}
```



## 参考

[[CoreLocation]位置情報を取得する]([https://qiita.com/chino_tweet/items/db3a536234a43a3c31d9#%E3%83%93%E3%83%AB%E3%83%89](https://qiita.com/chino_tweet/items/db3a536234a43a3c31d9#ビルド) "[CoreLocation]位置情報を取得する")

[位置情報から今の自分のいる場所をマップで表示するコード書いた](https://qiita.com/tacksman/items/0efa452f694aa7f47be1)

[爆速でMAPの表示、ピンを立てる方法@Swift 3](http://developers.goalist.co.jp/entry/2016/12/08/爆速でMAPの表示、ピンを立てる方法%40Swift_3)