iOS 8 の位置情報のプライバシー設定に対応する
iOS 7 では設定アプリの Privacy > Location Services から各アプリの位置情報のプライバシー設定ができました。
iOS 7 ではアプリに対して位置情報の使用を許可するか、許可しないかの 2 択しかありませんでしたが、iOS 8 では以下の 3 択に変わるようです。
- 許可しない (Never)
- 使用中のみ許可 (WhenInUse)
- 常に許可 (Always)
WhenInUse は、「アプリまたはその機能が画面上に表示されている場合のみ位置情報を利用することを許可」する設定です。
Always は、「バックグラウンドで実行中の場合にもアプリが位置情報を利用することを許可する」する設定です。
WhenInUse か Always かによって、Core Location で使える機能が変わります。
また、同じ Core Location の機能を使っていてもユーザからの見え方が若干変わります。
Core Location の機能の制限
プライバシー設定に関わる Core Location の各機能との対応は以下のようになります。
| Never | WhenInUse | Always | |
|---|---|---|---|
| 標準位置情報サービス (Standard Location Service) | ✔ | ✔ | |
| 大型変更位置情報サービス (Significant-Change Location Service) | ✔ | ||
| 地理的領域の観測 (Grographical Region Monitoring) | ✔ | ||
| ビーコン領域の観測 (Beacon Region Monitoring) | ✔ | ||
| ビーコンの距離測定 (Ranging) | ✔ | ✔ |
Standard Location Service を使って位置情報を取得したり、あるいは Ranging でビーコンとの距離測定をおこなうだけの場合には WhenInUse で足ります。
Significant-Change Location Service か Region Monitoring をおこなう場合には Always が必要になります。
冒頭でバックグランドで実行中のアプリが位置情報を取得するには Always が必要だと書きましたが、Standard Location Service にも Ranging にもバックグラウンドで更新を取得する機能がありますね。
なので、たとえば Standard Location Service でもバックグラウンドで位置情報を取得したい場合には、Always が必要な気もしますが、この場合でも WhenInUse でいいようです。
というのも、iOS 8 ではアプリがバックグラウンドで位置情報を使用している間は、ステータスバーが青くハイライトされるようになるので、「アプリまたはその機能が画面上に表示されている場合のみ位置情報を利用することを許可」するほうに入るというわけです。
デザリングしている最中は以下のようにステーテスバーが青くなりますが、これと同じような感じになります。
ユーザからの見え方の違い
青いバーが出るのは WhenInUse を許可されたアプリがバックグラウンドで位置情報を使用しているときだけで、Always の場合は青くならないようです。
また、ステータスバーをタップすることで直近に起動したアプリを開くことができるようになります。
もう一つ変化があるのが、以下のような位置情報を利用するときに表示される確認ダイアログです。
iOS 7 でも Info.plist の NSLocationUsageDescription キーで、ダイアログに独自のメッセージを指定することができました。
ここにはアプリがどのような目的で位置情報を使用するのかを説明するテキストを指定するわけですが、iOS 8 ではこのテキストの指定が必須になります。
さらに、Always の場合は一度ユーザが許可をしても、数日毎に再び確認ダイアログが表示されるようになるようです。
実装
実装の際には以下の 3 点に注意する必要があります。
- Info.plist 内の UsageDescription
- 認証状態の取得
- 認証リクエスト用 API
Info.plist 内の UsageDescription
iOS 6 以降は Info.plist 内の NSLocationUsageDescription キーでダイアログに任意のメッセージを表示することができました。
iOS 8 では NSLocationUsageDescription は depricated になり、WhenInUse と Always の 2 タイプに合わせて以下の 2 つのキーが追加されます。
NSLocationUsageDescription- NSLocationWhenInUseDescription
- NSLocationAlwaysUsageDescription
また、これまではオプショナルだったのが、iOS 8 では使用するタイプに対応するキーについては指定が必須になります。
認証状態の取得
位置情報の認証状態を示す CLAuthorizationStatus については kCLAuthorizationStatusAuthorized が depricated になり、新しく kCLAuthorizationStatusAuthorizedAlways と kCLAuthorizationStatusAuthorizedWhenInUse が追加されます。
- kCLAuthorizationStatusNotDetermined
- kCLAuthorizationStatusRestricted
- kCLAuthorizationStatusDenied
kCLAuthorizationStatusAuthorized- kCLAuthorizationStatusAuthorizedAlways
- kCLAuthorizationStatusAuthorizedWhenInUse
認証リクエスト用 API
iOS 8 では CLLocationManager に新しく追加される認証リクエスト API を呼び出すことで、認証ダイアログが表示されるようになります。
requestAlwaysAuthorizationrequestWhenInUseAuthorization
このメソッドは何度でも呼び出すことはできますが、ダイアログが表示されるのはステータスが NotDetermined の場合だけです。
したがって、一度ダイアログが表示されてユーザが許可する / しないの選択をした後は、このメソッドを呼び出しても何も起こりません。
iOS 7 をサポートする
iOS 7 をサポートする場合は引き続き NSLocationUsageDescription や kCLAuthorizationStatusAuthorized を使用します。
また、認証リクエスト API を使用する場合にはメソッドが使用可能かをチェックしてから実行します。
if locationManager.respondsToSelector("requestWhenInUseAuthorization") {
locationManager.requestWhenInUseAuthorization()
}
デモコード
以下は iOS 7 / 8 両方サポートする場合の Swift で書いたデモコードです。
import UIKit
class ViewController: UIViewController, CLLocationManagerDelegate {
var locationManager: CLLocationManager!
override func viewDidLoad() {
super.viewDidLoad()
if !locationManager { locationManager = CLLocationManager() }
locationManager.delegate = self
}
@IBAction func startUpdates(sender: AnyObject) {
locationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [CLLocation]!) {
NSLog("didUpdatesLocations")
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
NSLog("didFailWithError: \(error)")
}
func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .NotDetermined:
if locationManager.respondsToSelector("requestWhenInUseAuthorization") { locationManager.requestWhenInUseAuthorization() }
case .Restricted, .Denied:
self.alertLocationServicesDisabled()
case .Authorized, .AuthorizedWhenInUse:
break
default:
break
}
}
func alertLocationServicesDisabled() {
let title = "Location Services Disabled"
let message = "You must enable Location Services to track your run."
if NSClassFromString("UIAlertController") {
let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "Settings", style: .Default, handler: { action in
let url = NSURL(string: UIApplicationOpenSettingsURLString)
UIApplication.sharedApplication().openURL(url)
}))
alert.addAction(UIAlertAction(title: "Close", style: .Cancel, handler: nil))
presentViewController(alert, animated: true, completion: nil)
} else {
UIAlertView(title: title, message: message, delegate: nil, cancelButtonTitle: "Close").show()
}
}
}
CLLocationManager オブジェクトにデリゲートオブジェクトを設定すると、初回に locationManager:didChangeAuthorizationStatus: が呼ばます。
そこで、まだプライバシー設定がされてない状態 (NotDetermined) の場合は、requestWhenInUseAuthorization メソッドで認証ダイアログを表示します。
位置情報が使用できない状態 (Restricted or Denied) の場合は、位置情報の使用を許可して欲しいという自前のダイアログを表示しています。
(話がそれますが、iOS 8 で復活した設定アプリへの URL スキームを使っています)
また、Swift の場合は CLAuthorizationStatus の値が以下になります。
- NotDetermined
- Restricted
- Denied
- Authorized
- AuthorizedWhenInUse
Authorized については iOS 7 の場合はこれまでと同様の位置情報の使用が許可された状態、iOS 8 の場合は Always が許可された状態を示すようです。