LoginSignup
9
7

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-01-08

今回は、iOSアプリで位置情報を取得するときに配慮しなくてはいけない点を自分なりにまとめてみようと思います。

はじめに

私は今、現在地から飲食店を検索するアプリを開発しています。
※App Storeにリリースしているので良かったらダウンロードしてみて下さい!
ふっぴーくん

自分の現在地から飲食店を検索するアプリなので、もちろん、現在地を取得しなくてはいけません。だったら、現在地を取得して「はい、終わり!」というシンプルなものではありませんでした。

まずは、現在地を取得するために最低限必要な手順から書いていきたいと思います。

手順

iOSアプリで位置情報を取得するためには、

1.プライバシー情報取得の許可
2.CoreLocationフレームワークをインポート、CLLocationManagerクラスを初期化
3.位置情報サービスを使用するために、ユーザーに許可をリクエスト
4.CLLocationManagerDelegateプロトコルを採用し、デリゲートメソッドを実装

おおまかには、こんな感じの手順で位置情報が取得できます。
では、順を追って説明していきます。

1.プライバシー情報取得の許可

iOSアプリでは、プライバシーに関わる情報を取得する際に許可が必須です。
位置情報もプライバシーに関わる情報なので、位置情報サービスを使用する前に許可を貰わないと使うことができません。

許可を得るには、info.plistにて使いますよ!という意思表示を示しましょう。
Key:Location When In Use Usage Description
Value:任意の文字列(例:このアプリは位置情報サービスを使います。)

私の場合はこんな感じです↓
スクリーンショット 2021-01-08 11.43.11.png

2.フレームワークをインポート、クラスを初期化

まずは、位置情報関連のサービスがアプリ内で使えるようにCoreLocationフレームワークをインポートします。

ViewController
import CoreLocation

そして、CoreLocationフレームワークに含まれているCLLocationManagerクラスを初期化します。
このクラスは、位置情報の機能を管理してくれています。

このクラスを、メンバプロパティ(クラス内で定義されるプロパティ)として
ViewControllerで宣言しインスタンスを初期化していきます。

ViewController
import CoreLocation

final class ViewController: UIViewController {
 // CLLocationManagerクラスのインスタンスlocationManagerをメンバプロパティとして宣言
 var locationManager: CLLocationManager = {
  // インスタンスを初期化
  var locationManager = CLLocationManager()
  // 取得精度の設定(1km以内の精度)
  locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
  // 位置情報取得間隔(5m移動したら位置情報取得)
  locationManager.distanceFilter = 5
  return locationManager
 }()
}

CLLocationManagerクラスには、位置情報取得の精度位置情報取得の間隔など色々な設定を自分で調節できます。
宣言と同時に書いてあるdesiredAccuracydistanceFilterプロパティの事ですね。

自分のアプリに合った設定をすることで、バッテリーが長持ちするというメリットがあります。
位置情報サービスはかなりの電力を使いますので、こういう所も配慮しなくてはいけませんね。

3.ユーザーに許可をリクエスト

位置情報サービスを使用するときは、ユーザーに対して許可をリクエストしなくてはいけません。
今回は、アプリの使用中に許可をリクエストしたいのでCLLocationManagerクラスのメソッドrequestWhenInUseAuthorization()を使います。

ViewController
import CoreLocation

final class ViewController: UIViewController {
 // CLLocationManagerクラスのインスタンスlocationManagerをメンバプロパティとして宣言
 private var locationManager: CLLocationManager = {
  // インスタンスを初期化
  var locationManager = CLLocationManager()
  // 取得精度の設定(1km以内の精度)
  locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
  // 位置情報取得間隔(5m移動したら位置情報取得)
  locationManager.distanceFilter = 5
  return locationManager
 }()

 override func viewDidLoad() {
    super.viewDidLoad()
    // ユーザーに許可をリクエスト
    locationManager.requestWhenInUseAuthorization()
  }
}

ここで一旦、ビルドしてみたいと思います。

そもそも端末の位置情報サービスがオフになっているとこんな感じのアラートが表示されます。
設定を選択することで設定アプリに画面遷移しますので、タップして位置情報サービスをオンにしてみましょう。

※ただし、アプリをインストール後の初回起動のみしか表示されません。

位置情報サービスをオンにしてから再度、アプリを起動すると、こんな感じのアラートが表示されます。
このアラートは、requestWhenInUseAuthorization()を呼ぶと表示されます。

1度だけ許可Appの使用中は許可などの選択肢がありますね。
これは位置情報サービスの認証ステータスでいくつかの種類があります。

認証ステータスは、CLAuthorizationStatusにまとめており、
アプリを初めて起動した時はnotDeterminedになっています。

なので現状、ビルドしたアプリはnotDetermined状態という事ですね。

分かりやすいように認証ステータスをまとめてみました↓

認証ステータス 意味
authorizedAlways 常に許可
authorizedWhenInUse Appの使用中は許可(1度だけ許可も含まれる)
denied 許可しない
restricted 端末の位置情報サービスが許可されていない
notDetermined 位置情報サービスを使用できるかどうかを選択していない

因みに、requestWhenInUseAuthorization( )はnotDetermined以外だと呼ばれないです。
これはリクエストを許可しているのにアラートが表示されてしまうのを防ぐためだと思われます。

ユーザーにリクエストしたのはいいですが、まだ現時点では位置情報を取得することは出来ません。

4.デリゲートメソッドの実装

CLLocationManagerDelegateプロトコルを準拠し、デリゲートメソッドを実装していきます。

ViewController
import CoreLocation

final class ViewController: UIViewController {
 //CLLocationManagerクラスのインスタンスlocationManagerをメンバプロパティとして宣言
 private var locationManager: CLLocationManager = {
  // インスタンスを初期化
  var locationManager = CLLocationManager()
  // 取得精度の設定(1km以内の精度)
  locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
  // 位置情報取得間隔(5m移動したら位置情報取得)
  locationManager.distanceFilter = 5
  return locationManager
 }()

 override func viewDidLoad() {
    super.viewDidLoad()
    // ユーザーに許可をリクエスト
    locationManager.requestWhenInUseAuthorization()
    // デリゲート先を自分のViewControllerにする
    locationManager.delegate = self
  }
}

// ViewControllerにCLLocationManagerDelegateプロトコルを準拠
extention ViewController: CLLocationManagerDelegate {
    // CLLocationManagerクラスのインスタンス初期化および、認証ステータスが変更されたら呼ばれるメソッド
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        // アプリの現在の認証ステータス
        let status = manager.authorizationStatus

        switch status {
        case .authorizedAlways, .authorizedWhenInUse:
            // 位置情報取得を開始
            manager.startUpdatingLocation()

        case .notDetermined:
            // ユーザーに許可をリクエスト
            manager.requestWhenInUseAuthorization()

        case .denied:
            break

        case .restricted:
            break

        default:
            break
        }
    }
  // 位置情報を取得・更新したら呼ばれるメソッド
  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))")
    }
}

requestWhenInUseAuthorization()によって、認証ステータスが決定したら
locationManagerDidChangeAuthorizationデリゲートメソッドが呼ばれ、そのメソッド内で処理していきます。

とりあえずは、ユーザーが.authorizedAlways, .authorizedWhenInUseを選択した場合の処理と
.notDeterminedを選択した場合の処理を記述しています。

位置情報を取得するためには、startUpdatingLocation()メソッドを呼ばなければいけません。
このメソッドにより、位置情報取得を開始してくれます。

そして、位置情報を取得したらlocationManager(_: didUpdateLocations:)デリゲートメソッドが呼ばれ、
ここでやっと位置情報を取得することが出来ます。

では、長くなりましたが、ここからが本題となります。

本題

題名にも書いてあるように、位置情報を取得する時には配慮しなくてはいけない点がいくつかあります。
まずは、端末の位置情報サービスの有無です。

端末の位置情報サービスがオフだった場合、「位置情報サービスをオンにして下さい」というアラートが
表示されると思いますが、アプリをインストール後の初回起動のみしか表示されません。

なので、2回目以降の起動時は端末の位置情報サービスがオフだった場合、何も起こりません。
もちろん、位置情報を取得することも出来ません。

では、どうすれば良いのでしょうか?

端末の位置情報サービスの有無をチェックする

自分で端末の位置情報サービスの有無をチェックするコードを記述しなくてはいけません。

しかし、そんなに難しい訳ではなく、CLLocationManagerクラスには便利なメソッドがあります。
それは、locationServicesEnabled( )です。

このメソッドは、端末で位置情報サービスが有効になっているかどうかを示すBool値を返します。
trueの場合は位置情報サービスオン、falseの場合は位置情報サービスオフ

では、このメソッドを先ほどのソースコードに組み込んでいきましょう。

そして、実際にアラートを表示したいので
私が過去にQiitaに投稿した記事のやり方でアラートを表示したいと思います↓
UIAlertControllerをファイルを分けて実装してみる

ViewController
import CoreLocation

final class ViewController: UIViewController {
 //CLLocationManagerクラスのインスタンスlocationManagerをメンバプロパティとして宣言
 private var locationManager: CLLocationManager = {
  // インスタンスを初期化
  var locationManager = CLLocationManager()
  // 取得精度の設定(1km以内の精度)
  locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
  // 位置情報取得間隔(5m移動したら位置情報取得)
  locationManager.distanceFilter = 5
  return locationManager
 }()

 override func viewDidLoad() {
    super.viewDidLoad()
    // ユーザーに許可をリクエスト
    locationManager.requestWhenInUseAuthorization()
    // デリゲート先を自分のViewControllerクラスにする
    locationManager.delegate = self
  }
}

// ViewControllerにCLLocationManagerDelegateプロトコルを準拠
extention ViewController: CLLocationManagerDelegate {
    // CLLocationManagerクラスのインスタンス初期化および、認証ステータスが変更されたら呼ばれるメソッド
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        // 端末の位置情報サービスがオンの場合
        if CLLocationManager.locationServicesEnabled() {
        // アプリの現在の認証ステータス
        let status = manager.authorizationStatus

        switch status {
        case .authorizedAlways, .authorizedWhenInUse:
            // 位置情報取得を開始
            manager.startUpdatingLocation()

        case .notDetermined:
            // ユーザーに許可をリクエスト
            manager.requestWhenInUseAuthorization()

        case .denied:
            break

        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))")
    }
}

func locationManagerDidChangeAuthorization(_ manager: CLLocationManager)内で分岐させています。

位置情報サービスをオフの状態でビルドしてみると、ちゃんと表示されました。
とにかく、これでアプリは端末の位置情報サービスの有無をチェックするようになりましたね。

これで一件落着!と思いますが、ここから更に想定して実装していかなければいけません。

例えば、ユーザーがアラートでOKを選択して位置情報サービスをオフにしたままアプリに戻ったとしましょう。
どうなると思いますか?

流れとしてはこんな感じ↓
位置情報サービスオフ > アプリ起動 > アラート表示(OKを選択) > 設定アプリを開く > 何もしないまま戻る > ???

このままだと、アプリは何もチェックしません

この解決方法はアプリがバックグラウンド状態から戻った時に位置情報サービスの有無をチェックすれば解決します。
では、どのようにするのかをまとめようと思いましたが、記事が長くなってしまったので後日、書こうと思います。

ここまで読んで下さって、ありがとうございます!
もし、ここは違うよ!というのがありましたら気軽にコメントして下さい。

ここまでのソースコードも載せておきます。

ソースコード

ViewController
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
  }
}

extention 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:
            break

        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))")
    }
}
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)
    }
}

続き

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

9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7