[iOS] 位置情報の取得 (Swift3編)

1 はじめに

以前、「位置情報の取得」に関する記事を掲載させて頂きましたが、最新の状況で(iOS 8 〜 iOS 10)で少し状況が変わってきているのと、サンプルコードをSwift3に変更することで、再編させて頂きました。

[iOS] 位置情報の取得

位置情報取得に使用するCLLocationManagerには、以下のようなサービスがあります。

  1. standard location service (標準の位置情報)
  2. significant location change service (大幅な位置の変更)
  3. region-monitoring service (iBeaconによるリージョン境界の出入り)
  4. beacon ranging service (iBeaconへの近接測定)
  5. heading service (コンパス iOSのみ)
  6. visits service (訪問先の取得)

本記事は、このうちの「1. standard location service (標準の位置情報)」に関するものだけを扱っていますが、「ユーザの認可」に関する節は、全てのサービスにおいて共通となる内容です。

2 ユーザの認可

(1) ロケーションマネージャーの生成と認可ステータスの取得

CLLocationManagerを生成し、CLLocationManagerDelegateプロトコルを実装したクラスをdelegateに設定すると、位置情報取得に関する許可が変更された際に、locationManager(_:didChangeAuthorization:)でその状態を受け取ることができます。

そして、この認可ステータスに基いて、必要な許可をユーザーに求めたり、情報取得を開始したり、又は、設定変更を促すメッセージを表示するなどの処理を記述します。

import CoreLocation

class ViewController: UIViewController {

    var locationManager: CLLocationManager!

    override func viewDidLoad() {
        super.viewDidLoad()

        locationManager = CLLocationManager() // インスタンスの生成
        locationManager.delegate = self // CLLocationManagerDelegateプロトコルを実装するクラスを指定する

    }
}

extension ViewController: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        switch status {
        case .notDetermined:
            print("ユーザーはこのアプリケーションに関してまだ選択を行っていません")
            // 許可を求めるコードを記述する(後述)
            break
        case .denied:
            print("ローケーションサービスの設定が「無効」になっています (ユーザーによって、明示的に拒否されています)")
            // 「設定 > プライバシー > 位置情報サービス で、位置情報サービスの利用を許可して下さい」を表示する
            break
        case .restricted:
            print("このアプリケーションは位置情報サービスを使用できません(ユーザによって拒否されたわけではありません)")
            // 「このアプリは、位置情報を取得できないために、正常に動作できません」を表示する
            break
        case .authorizedAlways:
            print("常時、位置情報の取得が許可されています。")
            // 位置情報取得の開始処理
            break
        case .authorizedWhenInUse:
            print("起動時のみ、位置情報の取得が許可されています。")
            // 位置情報取得の開始処理
            break
        }
    }
}

(2) 利用目的の記載

LocationManagerを使用する場合は、「位置情報を利用する目的」をInfo.plistに記載する必要があります。

起動中のみ位置情報を取得する場合

<key>NSLocationWhenInUseUsageDescription</key>
<string>このアプリは、**のため、位置情報を取得します</string>

常に位置情報を取得する場合

<key>NSLocationAlwaysUsageDescription</key>
<string>このアプリは、**のため、常に位置情報を取得します</string>

設定例

001

ここで指定したメッセージは、ユーザーに許可を求める時、アラートダイアログに表示されますが、アプリが位置情報の取得を必要とする理由が明確に記述されていないと、審査でリジェクトとなる可能性があります。

この記載がない場合、iOS10では、以下のようなエラーがログに表示されます。(iOS 8 及び、iOS 9では、サイレントに失敗します)

This app has attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSLocationWhenInUseUsageDescription key with a string value explaining to the user how the app uses this data

(3) 認可リクエスト

ユーザーの許可を得るための認可リクエストには、以下の2種類があります。(注:iOS7でこのメソッドは使用できません)

  • requestWhenInUseAuthorization() 起動中のみ許可
  • requestAlwaysAuthorization() 常時許可

まだユーザーの設定が行われていない場合に、認可を求めるコードは、次のようになります。

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    switch status {
    case .notDetermined:
        // アプリケーションに関してまだ選択されていない
        locationManager.requestWhenInUseAuthorization() // 起動中のみの取得許可を求める
        break
    case .authorizedWhenInUse:
        print("位置情報取得(起動時のみ)が許可されました")
        break
    case .denied:
        print("位置情報取得が拒否されました")
        break

    // ・・・省略・・・


認可リクエストをコールすると、次のような許可を求めるダイアログが表示され、ユーザの選択によって、認可ステータスが変わります。

002

このAPIは、認可ステータスが .notDetermined の時、及び、権限の昇格(「起動中のみ」から「常時」への変更)の時にしか利用できません。

ユーザーによって選択された認可のステータスは、システムに保存され、アプリから変更することは出来ません。

「設定」 > 「プライバシー」 > 「位置情報サービス」> 「アプリ名」

003

認可ステータスが有効になっていなくても、ロケーションサービスを開始することは可能です。しかし、認可ステータスが許可状態になるまで、アプリで位置データを取得することはできません。

3 標準的な位置情報

(1) 継続的な位置情報の取得

CLLocationManagerstartUpdatingLocation()メソッドで、位置情報の取得を開始できます。 (stopUpdatingLocation()で停止)

位置情報は、変化するたびにCLLocationManagerDelegateプロトコルのlocationManager(_:didUpdateLocations:)が呼ばれ、ここで現在位置を取得できます。

extension ViewController: CLLocationManagerDelegate {

    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        switch status {
        case .authorizedWhenInUse:
            // 位置情報の取得開始
            locationManager.startUpdatingLocation() 
            break

        // ・・・省略・・・
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

        for location in locations {
                print("緯度:\(location.coordinate.latitude) 経度:\(location.coordinate.longitude) 取得時刻:\(location.timestamp.description)")
        }
    }
}

以下は、出力例です。

緯度:35.4223881357936 経度:139.486972986368 取得時刻:2017-01-29 14:54:15 +0000
緯度:35.4223881357936 経度:139.486972986368 取得時刻:2017-01-29 14:54:16 +0000
緯度:35.4223881357936 経度:139.486972986368 取得時刻:2017-01-29 14:54:17 +0000
緯度:35.4223881357936 経度:139.486972986368 取得時刻:2017-01-29 14:54:18 +0000
緯度:35.4223881357936 経度:139.486972986368 取得時刻:2017-01-29 14:54:19 +0000

(2) 一度だけの位置情報の取得

iOS 9以降では、一度だけ位置情報を取得するメソッドrequestLocation()が利用可能です。

このメソッドを利用する場合は、失敗時に呼ばれるlocationManager(_:didFailWithError:)の実装が必須となります。

requestLocation()は、非同期で動作し、情報の取得は、先と同じくlocationManager(_:didUpdateLocations:)で受け取ります。(取得には数秒かかります)

取得後、ロケーションサービスは、直ちに停止されるため、継続的な取得が必要ない場合に有効ですが、情報取得に時間がかかる場合、不正確な(誤差が大きい)情報が返される可能性がありますので、注意が必要です。

class ViewController: UIViewController {
    // ・・・省略・・・

    @IBAction func tapRequestLocationButton(_ sender: Any) {
        if #available(iOS 9.0, *) {
            locationManager.requestLocation() // 一度きりの取得
        }
    }
    // ・・・省略・・・
}

extension ViewController: CLLocationManagerDelegate {
    // ・・・省略・・・

    // requestLocation()を使用する場合、失敗した際のDelegateメソッドの実装が必須
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print("位置情報の取得に失敗しました")
    }
}


(3) 精度に関するプロパティ

位置情報の精度を指定するプロパティに次のものがあります。

  • activityType 更新に関連するユーザのアクテビティ
  • desiredAccuracy 距離精度(m)
  • distanceFilter 更新頻度に関わる移動距離(m)

必要最低限の精度を指定することで、電力消費を抑えることができます。

例えば、希望精度を1Kmに設定すると、GPSをオフにし、Wifiやセルラー無線に依存した動作が可能になるため、大幅な電力節約が可能です。正確度が高いほど、より多くの電力を消費するGPSのようなハードウェアの使用が必要になります。

activityType

このプロパティーの設定によって位置情報の更新をどれ位一時停止出来るかを判断します。

locationManager.activityType = .fitness

下記の値が指定可能です。

  • other その他(デフォルト値)
  • automotiveNavigation 自動車ナビゲーション用
  • fitness 歩行者
  • otherNavigation その他のナビゲーションケース(ボート、電車、飛行機)

desiredAccuracy

必要な精度を指定します。

locationManager.desiredAccuracy = kCLLocationAccuracyKilometer

指定可能な定数に以下のものがありますが、m単位でも指定可能です。

  • kCLLocationAccuracyBestForNavigation ナビゲーションに最適な値
  • kCLLocationAccuracyBest 最高精度(iOS,macOSのデフォルト値)
  • kCLLocationAccuracyNearestTenMeters 10m
  • kCLLocationAccuracyHundredMeters 100m(watchOSのデフォルト値)
  • kCLLocationAccuracyKilometer 1Km
  • kCLLocationAccuracyThreeKilometers 3Km

指定された精度を達成するための最善を尽くされますが、実施の精度は保証されません。当然ですが、精度を高く指定すればするほど消費電力は大きくなります。

distanceFilter

更新イベントの生成に必要な、水平方向の最小移動距離(メートル単位)を指定します。

locationManager.distanceFilter = 100.0

このプロパティのデフォルト値はkCLDistanceFilterNone(すべての動きを通知)です。

4 シュミレーターによる位置情報

シュミレーターで実行している場合、Xcodeの矢印記号で、予めセットされている座標を模擬的に返す事が出来ます。

004

また、GPXファイルを作成することで、任意の位置をシュミレートすることも出来ます。

GPXファイルとは、位置情報を扱うXML形式のファイルです。

たテキストファイルで、waypoint形式のサンプルは次のようなデータになっています。

<?xml version="1.0"?>
<gpx version="1.1" creator="drive.py coded by basuke">
    <wpt lat="35.681507" lon="139.765581">
        <name>Tokyo Station, 1 Chome-9 Marunouchi, Chiyoda, Tokyo, Japan</name>
    </wpt>
</gpx>

これをプロジェクトに追加すると、これを選択することが可能になります。

005

5 最後に

今回は、standard location service (標準の位置情報)に関してまとめてみました。

この情報だけを扱うのであれば、特に気を使う必要はないのですが、もし、LocationManagerの他のサービス(iBeacon関連など)を同時に扱うのであれば、「ユーザの認可」については、共通ですので、全てのサービスで、最大限必要な認可を取得する必要があることに注意が必要です。

6 参考資料


API Reference CLLocationManager
API Reference CLLocationManagerDelegate
[iOS] 位置情報の取得