前回の続き↓
iOSアプリで位置情報を取得するときに配慮する点をまとめてみた①
#はじめに
前回の記事を読んでない方に、簡単に説明するとiOSアプリで位置情報を取得するときに
配慮する点をまとめた内容となっています。
#前回までのあらすじ
前回、端末の位置情報サービスの有無
のチェックは
アプリをインストール後の初回起動のみ
しかされないと書きました。
初回起動時に位置情報サービスがオフ
になっていたらこんなアラートを表示します↓
このチェックは初回起動のみ
なので、2回目以降の起動時はアラートを表示するように改修しましたね↓
今回は、バックグラウンド状態
からフォアグラウンド状態
にアプリを切り替えた時にも
端末の位置情報サービスの有無
をチェックするようにしていきましょう。
#バックグラウンド状態から戻った時にチェックする(端末)
アプリが、バックグラウンド状態
からフォアグラウンド状態
に切り替わった時に
端末の位置情報サービスの有無
をチェックするように処理するには
AppDelegate
のapplicationWillEnterForeground(_ application: UIApplication)にて行います。
※AppDelegate
の呼ばれる順番は、こちらの記事が非常に分かりやすかったので載せておきます↓
iOSアプリのライフサイクル
// アプリがフォアグラウンド状態に入ろうとしている時に呼ばれるメソッド
func applicationWillEnterForeground(_ application: UIApplication)
ですが、アラートを表示したいのでViewControllerのクラス内で処理を行いたいですね。
アプリの状態が変わったことをAppDelegate
以外で感知して
ある処理を行いたい場合は、NotificationCenterを使用します。
※こちらの記事を参考にして実装しました↓
[Swift3.0] NotificationCenter を使ってアプリの状態に応じた処理を行う
そして今回も、アラートの表示はこちらの方法で表示します↓
UIAlertControllerをファイルを分けて実装してみる
// 初回表示以外にもバックグラウンド復帰、タブ切り替えなどにも呼ばれるメソッド
// まだビューが表示されていないため、計算コストの高い処理は避ける
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// iOSからの通知(UIApplication.willEnterForegroundNotification)を受信して登録されたメソッド(willEnterForeground)を呼び出す
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
// 画面が非表示になる直前に呼ばれるメソッド
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// 常に監視されているクラス(アプリで共通、UserDefaultと同じ感じ)なので呼び出したら毎回、削除(バグが起こりやすいため)
// 例えば、NavigationControllerを使ってViewController1 > 2 > 3に画面遷移し、全てのViewControllerでNotificationCenterを呼び、willEnterForegroundNotificationのメソッドを書いていたとする
// 画面遷移したとしても、ViewController 1,2,3は消えない(メモリー上に残っている)ので
// ViewController3の画面内でバックグラウンド復帰した場合、他のViewController1,2も反応してしまうため毎回、削除しなくてはならない
NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
}
// バックグラウンド状態から戻ってきた時に呼ばれるメソッド(自分で作る)
@objc private func willEnterForeground() {
// バックグラウンド状態から戻ってきた時に端末の位置情報サービスがオフの場合
if !CLLocationManager.locationServicesEnabled() {
// アラート表示
Alert.okAlert(vc: self, title: "位置情報サービスを\nオンにして下さい", message: "「設定」アプリ ⇒「プライバシー」⇒「位置情報サービス」からオンにできます")
}
}
ビルドしてみると、バックグラウンド状態
から戻った時にもアラートが表示されるようになりました↓
これで、端末の位置情報サービスの有無を起動時
とバックグラウンド状態から戻った時
に
チェックするようになりましたね。
後、チェックしなければいけないのはアプリの位置情報サービスの有無
です。
#アプリの位置情報サービスの有無をチェックする
位置情報を取得する際には、ユーザーに許可を貰わなければ取得出来ません。
なので、アプリはこのようなアラートを表示してユーザーに許可をリクエストします↓
ですが、もし、ユーザーが許可しない
を選択したらどうでしょうか?
位置情報が必要なアプリの場合、許可を貰わなければ困りますよね。
なのでユーザーが、許可しない
を選択した場合にアラートなどを表示したりして
アプリの位置情報取得の許可
を促さなければいけません。
前回のソースコードを例に説明します。
※ちょっと長いので一部抜粋
import UIKit
import CoreLocation
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
}
extension 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:
// アラートを表示して、アプリの位置情報サービスをオンにするように促す
Alert.okAlert(vc: self, title: "アプリの位置情報サービスを\nオンにして下さい", message: "")
case .restricted:
break
default:
break
}
}else {
Alert.okAlert(vc: self, title: "位置情報サービスを\nオンにして下さい", message: "「設定」アプリ ⇒「プライバシー」⇒「位置情報サービス」からオンにできます")
}
}
}
許可しない
を選択した場合、アラートがちゃんと表示されました。
これでも、ちゃんとユーザーに対してアプリの位置情報取得の許可
を促していますが
ここで、もう少し配慮してアラートのOK
を選択したら設定アプリ
に画面遷移するようにしましょう。
#設定アプリに画面遷移する
設定アプリ
に画面遷移するメリットとしてアプリの位置情報取得の許可
がスムーズになり
ユーザーの手間が省かれることです。
画面遷移する方法は、こちらの記事が分かりやすかったので載せておきます↓
Swift5を使ってURLスキームで設定画面に遷移する方法
この方法を用いて、先ほどのソースコードに追加していきます。
import UIKit
import CoreLocation
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
}
extension 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:
// アラートを表示して、アプリの位置情報サービスをオンにするように促す
// ユーザーに対して分かりやすようにmessageで、OKを選択すると設定アプリに画面遷移することを伝える
Alert.okAlert(vc: self, title: "アプリの位置情報サービスを\nオンにして下さい", message: "OKをタップすると設定アプリに移動します") { (_) in
// OKを選択した後、設定アプリに画面遷移する
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
}
case .restricted:
break
default:
break
}
}else {
Alert.okAlert(vc: self, title: "位置情報サービスを\nオンにして下さい", message: "「設定」アプリ ⇒「プライバシー」⇒「位置情報サービス」からオンにできます")
}
}
}
こちらをビルドしてみると、ちゃんと設定アプリ
に画面遷移しました。
ですが、ここでも落とし穴があります。
この方法で、設定アプリに移動したのはいいけど
アプリの位置情報取得の許可
をしないまま戻ったとしましょう。
このままだと、アプリは何もチェックしません。
この場面でも、アプリがバックグラウンド状態
から戻った時に
アプリの位置情報取得の有無
をチェックしなければいけません。
またまた記事が長くなりそうなので、今回はここら辺で終わります。
ここ間違っているよー!というのがありましたら、気軽にコメントして下さい。
最後まで読んで下さって、ありがとうございます!
ここまでのソースコードを下に載せておきます↓
#ソースコード
import UIKit
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
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
}
@objc private func willEnterForeground() {
if !CLLocationManager.locationServicesEnabled() {
Alert.okAlert(vc: self, title: "位置情報サービスを\nオンにして下さい", message: "「設定」アプリ ⇒「プライバシー」⇒「位置情報サービス」からオンにできます")
}
}
}
extension 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:
Alert.okAlert(vc: self, title: "アプリの位置情報サービスを\nオンにして下さい", message: "OKをタップすると設定アプリに移動します") { (_) in
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
}
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))")
}
}
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)
}
}