iOS Developer Libraryの「位置情報対応プログラミングガイド」(英語版はMaking Your Application Location-Aware)に沿って、サンプルを公開します。
注意点
カメラ・加速度センサー・ジャイロスコープ・磁力センサーなどのハードウェアを利用する場合とは、以下の点で異なります。- 位置情報サービスを利用する場合は、ユーザに確認を取らなければならない
- 確認用のダイアログはフレームワークが自動的に表示してくれます。必要になる直前に確認するようにしなければ、ユーザが「なぜその機能が必要なのか?」という懸念をいだくことがあります。
- 機内モードなど、デバイスが位置情報を利用できない場合がある
- 一度でも位置情報サービスの取得に失敗すると次回も失敗する可能性が高くなるため、電池のことを考えて、しばらく休止するなどの処理が必要になります。
- ユーザが意図的に機能をオフにしているかもしれない
- 使って欲しいがゆえに何度も確認メッセージを表示したりするのは、ユーザが不快に感じる可能性があります。位置情報サービスが必須なアプリでなければ、表示方法を検討すべきです。
ユーザの位置情報の利用
単純にユーザの位置情報を利用するだけでも、以下の3種類が用意されています。- 標準位置情報サービス(Standard Location Service)【iOS 全バージョン】
- 携帯電話基地局・Wi-Fiスポット・GPSを組み合わせて現在地を取得します。携帯電話機能やGPSが付いていないデバイスであってもOS側で適切に判断して使用するハードウェアを選択します。
- 基地局・Wi-Fi・GPSのいずれかの情報を取得できた順に利用します。そのため、検出直後などは精度が悪いことがありますが、徐々に高精度になります。総合すると、最も正確に現在位置を検出できるが、最終的な検出までに時間がかかります。また電池の消費量も最も多くなります。バックグラウンドや終了した時に位置情報を取得することはできません。
- 例外として、<project-name>-Info.plistのUIBackgroundModesキーに「location」を追記すればバックグラウンドでも動作させ続けることができますが、電池を大量に消費します。(詳細は、「iOSアプリケーションプログラミングガイド」の62ページ目「ユーザの位置情報の追跡」を参照してください。)
- 大幅変更位置情報サービス(Significant-Change Location Service)【iOS 4.0以降】
- 携帯電話基地局を利用し、大まかな現在地を取得します。大まかとは言っても通常利用するには十分な精度で、現在位置の検出も早く、省電力です。そして、アプリがバックグラウンド動作中や終了していてもアプリを復帰させることができます。しかし、基地局を利用するため、iPhoneやiPad WiFi+Cellular(3G)などでしか利用できません。
- 領域観測(Region Monitoring)【iOS 4.0以降】
- 指定された場所に近づいた時や、出て行く時を検出します。その際、アプリがバックグラウンド動作中や終了していても設定した条件でアプリを復帰させることができます。
実装手順
- 「CoreLocation.framework」の追加
- 必要に応じて、<project-name>-Info.plistファイルにキー「location-services」「gps」を追加する。
- ただし、現在位置の情報やGPSが無くともアプリケーションが正常に動作する場合には追加すべきではありません。(UIRequiredDeviceCapabilitiesについての詳細は「iOSデバイス間の互換性まとめ」をご覧下さい。)
- ただし、現在位置の情報やGPSが無くともアプリケーションが正常に動作する場合には追加すべきではありません。(UIRequiredDeviceCapabilitiesについての詳細は「iOSデバイス間の互換性まとめ」をご覧下さい。)
- ヘッダのインポート
#import <CoreLocation/CoreLocation.h>
- CLLocationManagerDelegateプロトコルの宣言
@interface ViewController : UIViewController <CLLocationManagerDelegate> @end
- 位置情報サービスの開始
- 位置情報の受け取り
- (不必要になったら)位置情報サービスの停止
標準位置情報サービス
- 位置情報サービスの開始
iOS 3.xにも対応させる必要がある場合はプロパティ【Deprecated in iOS 4.0】を使います。// プロパティではなくクラスメソッドです if ([CLLocationManager locationServicesEnabled]) { // インスタンスを生成し、デリゲートの設定 _manager = [[CLLocationManager alloc] init]; _manager.delegate = self; // 取得精度 _manager.desiredAccuracy = kCLLocationAccuracyBest; // 更新頻度(メートル) _manager.distanceFilter = kCLDistanceFilterNone; // サービスの開始 [_manager startUpdatingLocation]; }
// インスタンスの生成 _manager = [[CLLocationManager alloc] init]; if (_manager.locationServicesEnabled || [CLLocationManager locationServicesEnabled]) { // 必要な設定をして、サービスの開始 }
- 位置情報の受け取り(標準位置情報サービス・大幅変更位置情報サービスで共通)
// 標準位置情報サービス・大幅変更位置情報サービスの取得に成功した場合 - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation { // ここで任意の処理 NSLog(@"%s | %@, %@", __PRETTY_FUNCTION__, newLocation, oldLocation); } // 標準位置情報サービス・大幅変更位置情報サービスの取得に失敗した場合 - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { // ここで任意の処理 NSLog(@"%s | %@", __PRETTY_FUNCTION__, error); if ([error code] == kCLErrorDenied) { // 位置情報の利用が拒否されているので停止 [manager startUpdatingLocation]; } }
- (不必要になったら)位置情報サービスの停止
if ([CLLocationManager locationServicesEnabled]) { [_manager stopUpdatingLocation]; }
大幅変更位置情報サービス
- 位置情報サービスの開始
// プロパティではなくクラスメソッドです if ([CLLocationManager locationServicesEnabled]) { // インスタンスを生成し、デリゲートの設定 _manager = [[CLLocationManager alloc] init]; _manager.delegate = self; // 使用可能かの確認 if ([CLLocationManager significantLocationChangeMonitoringAvailable]) { // サービスの開始 [_manager startMonitoringSignificantLocationChanges]; } }
- 位置情報の受け取り(標準位置情報サービス・大幅変更位置情報サービスで共通)
// 標準位置情報サービス・大幅変更位置情報サービスの取得に成功した場合 - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation { // ここで任意の処理 NSLog(@"%s | %@, %@", __PRETTY_FUNCTION__, newLocation, oldLocation); } // 標準位置情報サービス・大幅変更位置情報サービスの取得に失敗した場合 - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { // ここで任意の処理 NSLog(@"%s | %@", __PRETTY_FUNCTION__, error); if ([error code] == kCLErrorDenied) { // 位置情報の利用が拒否されているので停止 [manager stopMonitoringSignificantLocationChanges]; } }
- (不必要になったら)位置情報サービスの停止
if ([CLLocationManager locationServicesEnabled] && [CLLocationManager significantLocationChangeMonitoringAvailable]) { [_manager stopMonitoringSignificantLocationChanges]; }
領域観測
- 位置情報サービスの開始
// プロパティではなくクラスメソッドです if ([CLLocationManager locationServicesEnabled]) { // インスタンスを生成し、デリゲートの設定 _manager = [[CLLocationManager alloc] init]; _manager.delegate = self; // 使用可能かの確認 if ([CLLocationManager regionMonitoringAvailable] && [CLLocationManager regionMonitoringEnabled]) { // 設定したい場所の情報を作成 CLLocationCoordinate2D location = CLLocationCoordinate2DMake(37.332708, -122.030336); // 半径、キー文字列をもとにオブジェクトを生成 CLRegion *region = [[CLRegion alloc] initCircularRegionWithCenter:location radius:100 identifier:@"Apple"]; // サービスの開始 [_manager startMonitoringForRegion:region desiredAccuracy:kCLLocationAccuracyBest]; // 不要なものを解放 [region release]; } }
- 位置情報の受け取り
// 領域観測の登録に失敗した場合 - (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error { // ここで任意の処理 NSLog(@"%s | %@, %@", __PRETTY_FUNCTION__, region, error); } // 指定した領域に入った場合 - (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region { // ここで任意の処理 NSLog(@"%s | %@", __PRETTY_FUNCTION__, region); } // 指定した領域から出た場合 - (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region { // ここで任意の処理 NSLog(@"%s | %@", __PRETTY_FUNCTION__, region); }
- (不必要になったら)位置情報サービスの停止
if ([CLLocationManager locationServicesEnabled]) { if ([CLLocationManager regionMonitoringAvailable] && [CLLocationManager regionMonitoringEnabled]) { // (不必要になったら)位置情報サービスの停止 for (CLRegion *region in _manager.monitoredRegions) { // 登録してある地点を全て取得し、停止 [_manager stopMonitoringForRegion:region]; } } }
CLLocationManager
ユーザによる位置情報サービスの許可について
- 標準位置情報サービス・大幅変更位置情報サービスの「オン」「オフ」【iOS 4.0以降】
iOS 3.xでも使いたい場合【Deprecated in iOS 4.0】+ (BOOL)locationServicesEnabled __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_0);
「設定 > 位置情報サービス」の値を参照して、「オン」の場合"YES"を返す。位置情報サービスが「オフ」の場合は"NO"だが、以下の場合は"YES"となります。@property(readonly, nonatomic) BOOL locationServicesEnabled __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_NA,__MAC_NA,__IPHONE_2_0,__IPHONE_4_0);
- 機内モード(圏外)
- アプリ毎の位置情報サービスの設定が「オフ」
- 機能制限がかかっていても、位置情報サービスは「オン」
- 領域観測の「オン」「オフ」【iOS 4.0以降】
startMonitoringForRegionだけを実行してみたが、位置情報サービスを利用するかのダイアログが表示される。上記の「locationServicesEnabled」との違いがわからなかった。+ (BOOL)regionMonitoringEnabled __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_0);
- 位置情報の利用可否についての詳細【iOS 4.2以降】
「設定 > 位置情報サービス」の値を参照して、以下の値を返す。+ (CLAuthorizationStatus)authorizationStatus __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_2);
typedef enum { kCLAuthorizationStatusNotDetermined = 0, // User has not yet made a choice with regards to this application kCLAuthorizationStatusRestricted, // This application is not authorized to use location services. Due // to active restrictions on location services, the user cannot change // this status, and may not have personally denied authorization kCLAuthorizationStatusDenied, // User has explicitly denied authorization for this application, or // location services are disabled in Settings kCLAuthorizationStatusAuthorized // User has authorized this application to use location services } CLAuthorizationStatus;
定数 説明 kCLAuthorizationStatusNotDetermined アプリ起動後に位置情報サービスを「利用する」か「しない」かの選択をまだしていない状態。表示されたダイアログの「OK」を選択すると位置情報サービスが利用できるようになる。「許可しない」を選択すると位置情報サービスが使えず、有効にするためにはユーザが「設定 > 位置情報サービス」で有効にしなければならない。 kCLAuthorizationStatusRestricted 機能制限(ペアレントコントロール)により位置情報サービスの利用開始が許可されていない状態。機能制限を解除してもらわない限り、ダイアログも表示されず、位置情報サービスの設定や利用をすることができない。 kCLAuthorizationStatusDenied ユーザが(意図的に)位置情報サービスの利用を拒否している状態。位置情報サービス全体のオフと、アプリ毎の位置情報サービスのオフの両方が含まれます。確認ダイアログが1回だけ表示され、以降は表示されなくなる。 kCLAuthorizationStatusAuthorized ユーザにより位置情報サービスの利用が許可されている状態。 それぞれの状態がどのような流れで遷移するのか
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) { // UIAlertViewなどでメッセージを表示する }
デバイスがサービスを使える機能を備えているかどうか
- 大幅変更位置情報サービス【iOS 4.0以降】
デバイスに、携帯電話基地局の電波を利用できる機能があるかを確認する。+ (BOOL)significantLocationChangeMonitoringAvailable __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_0)
- 領域観測【iOS 4.0以降】
デバイスに、携帯電話基地局の電波を利用できる機能があるかを確認する。+ (BOOL)regionMonitoringAvailable __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_0);
標準位置情報サービスの挙動を設定(大幅変更位置情報サービスでは使われません)
- 次回の更新までの距離【iOS 2.0以降】
一定距離を動いたら位置更新イベントを生成するかを指定する。単位はメートル。kCLDistanceFilterNoneを指定すると、距離ではなく、動く度に位置情報イベントが通知されるようになります。デフォルトはkCLDistanceFilterNoneです。@property(assign, nonatomic) CLLocationDistance distanceFilter; // 定義 typedef double CLLocationDistance;
- 位置情報の精度【iOS 2.0以降】
位置情報取得の精度を指定する。精度が良ければ良いほど正確だが、その分、電池の消費量も大きく増える。なお、距離はおおまかなものであり、保証されたものではありません。@property(assign, nonatomic) CLLocationAccuracy desiredAccuracy; // 定義 typedef double CLLocationAccuracy;
定数 説明 kCLLocationAccuracyBestForNavigation 最高レベル。ナビアプリなどで使用されることを想定され、充電状態でのみ使用されるようになっている。【iOS 4.0以降】 kCLLocationAccuracyBest iOS 2.0/3.0での最高レベル。(恐らく数m以内の誤差ですが、詳細は不明です)【iOS 2.0以降】 kCLLocationAccuracyNearestTenMeters 10m以内の誤差【iOS 2.0以降】 kCLLocationAccuracyHundredMeters 100m以内の誤差【iOS 2.0以降】 kCLLocationAccuracyKilometer 1km程度の誤差【iOS 2.0以降】 kCLLocationAccuracyThreeKilometers 3km程度の誤差【iOS 2.0以降】
ダイアログメッセージ
- メッセージの追加【iOS 3.2以降】
ダイアログメッセージの下部に文字列を追加する。もちろん、位置情報サービスの開始よりも先に代入しなければならない。@property(copy, nonatomic) NSString *purpose __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_3_2);
位置情報サービスの利用開始
- 標準位置情報サービス【iOS 全バージョン】
- (void)startUpdatingLocation;
- 大幅変更位置情報サービス【iOS 4.0以降】
- (void)startMonitoringSignificantLocationChanges __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_0);
- 領域観測【iOS 4.0以降】
- (void)startMonitoringForRegion:(CLRegion *)region desiredAccuracy:(CLLocationAccuracy)accuracy __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_0); - (void)startMonitoringForRegion:(CLRegion *)region __OSX_AVAILABLE_STARTING(__MAC_TBD,__IPHONE_5_0);
位置情報サービスの利用停止
- 標準位置情報サービス【iOS 全バージョン】
- (void)stopUpdatingLocation;
- 大幅変更位置情報サービス【iOS 4.0以降】
- (void)stopMonitoringSignificantLocationChanges __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_0);
- 領域観測【iOS 4.0以降】
- (void)stopMonitoringForRegion:(CLRegion *)region __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_0);
情報の取得
- 現在値の取得【iOS 2.0以降】
最新の位置情報が格納されている。大幅変更位置情報サービスや領域観測をトリガーとしたアプリの再起動時や、デリゲートを利用しない場合にも使える。@property(readonly, nonatomic) CLLocation *location;
- 領域観測で登録できる地点の半径【iOS 4.0以降】
これよりも大きな半径を観測することはできない。@property (readonly, nonatomic) CLLocationDistance maximumRegionMonitoringDistance __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_0);
- 領域観測に登録している地点一覧【iOS 4.0以降】
「startMonitoringForRegion」で登録した領域観測する地点の情報が格納されている。@property (readonly, nonatomic) NSSet *monitoredRegions __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_0);
CLLocationManagerDelegate
位置情報サービスの「オン」「オフ」の検知
- 位置情報サービスの設定が変更された場合【iOS 4.2以降】
位置情報サービスの設定が変更された場合に呼ばれる。- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_2);
定数 説明 kCLAuthorizationStatusNotDetermined 位置情報のリセットをした場合など kCLAuthorizationStatusRestricted 機能制限で位置情報サービスの利用を「オフ」から変更できないようにした場合 kCLAuthorizationStatusDenied 位置情報サービスを「オフ」にした場合 kCLAuthorizationStatusAuthorized 位置情報サービスを「オン」にした場合
標準位置情報サービス・大幅変更位置情報サービス
- 位置情報の更新時【iOS 2.0以降】
位置情報が更新された際に呼ばれる。newLocationの値はキャッシュされた古い情報かもしれないので、timestampを参照した方が良い。- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation;
- バックグラウンドで動作中の場合
- フォアグラウンドで動作中と同じメソッドが呼ばれるが、以下の方法で判別できる。詳細は「iphone - Behaviour for significant change location API when terminated/suspended? - Stack Overflow」を参照。
[UIApplication sharedApplication].applicationState == UIApplicationStateBackground
- フォアグラウンドで動作中と同じメソッドが呼ばれるが、以下の方法で判別できる。詳細は「iphone - Behaviour for significant change location API when terminated/suspended? - Stack Overflow」を参照。
- アプリが終了していた場合
- 「application:didFinishLaunchingWithOptions」に「UIApplicationLaunchOptionsLocationKey」がセットされている。その後、「locationManager:didUpdateToLocation:fromLocation:」が呼ばれる。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ...(snip)... if ([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey] ) { // 受信時の処理 } ...(snip)... }
- 「application:didFinishLaunchingWithOptions」に「UIApplicationLaunchOptionsLocationKey」がセットされている。その後、「locationManager:didUpdateToLocation:fromLocation:」が呼ばれる。
- バックグラウンドで動作中の場合
- 位置情報の取得に失敗した場合【iOS 2.0以降】
位置情報の取得に失敗した場合に呼ばれる。しょっちゅう失敗するものなので、必ず考慮しなければならない。なお、errorがkCLErrorDeniedの場合はユーザが意図的に拒否したものなので、必ず位置情報サービスの利用を停止しなければならない。- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error;
領域観測
- 領域に入った場合【iOS 4.0以降】
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_0);
- 領域から出た場合【iOS 4.0以降】
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_0);
- 領域の登録に失敗した場合【iOS 4.0以降】
- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_0);
CLLocation
座標情報
- 緯度(latitude)・経度(longitude)【iOS 2.0以降】
@property(readonly, nonatomic) CLLocationCoordinate2D coordinate; // 定義 typedef struct { CLLocationDegrees latitude; CLLocationDegrees longitude; } CLLocationCoordinate2D; typedef double CLLocationDegrees;
- 海抜高度【iOS 2.0以降】
マイナスの場合は海の中となる。@property(readonly, nonatomic) CLLocationDistance altitude;
取得した情報の精度
- 緯度・経度【iOS 2.0以降】
デバイスが取得した位置情報の中には大幅に精度の悪いものもある。そのため、これでどの程度の精度のものかを比較することができる。@property(readonly, nonatomic) CLLocationAccuracy horizontalAccuracy;
- 海抜高度【iOS 2.0以降】
リファレンスには「iOSデバイスでは常にマイナスの値となる」と書かれていますが、サンプルプログラムでは取得できています。(それが正しい値かは確認できていません)@property(readonly, nonatomic) CLLocationAccuracy verticalAccuracy;
移動情報
- 取得した時刻【iOS 2.0以降】
通常、位置情報の取得は時間が掛かるものなので、計測完了までの間にキャッシュを返すことがある。そのため、古い情報などは位置情報場大幅にずれていることもある。そのような位置情報を判別するために使われる。公式ドキュメントでは、15秒より古い情報は無視しています。@property(readonly, nonatomic) NSDate *timestamp;
- 距離【iOS 3.2以降】
2点間の距離を測る。ただし、高度は考慮しない。- (CLLocationDistance)distanceFromLocation:(const CLLocation *)location __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_3_2); // 【Deprecated in iOS 3.2】 - (CLLocationDistance)getDistanceFrom:(const CLLocation *)location __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_NA,__MAC_NA,__IPHONE_2_0,__IPHONE_3_2); // 定義 typedef double CLLocationDistance;
- 速度【iOS 2.2以降】
前回の場所から現在の場所への移動距離・時間を使い、速度を取得する。ただし、厳格なものではない為、あくまでの目安。@property(readonly, nonatomic) CLLocationSpeed speed __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_2_2); // 定義 typedef double CLLocationSpeed;
- 角度【iOS 2.2以降】
前回の場所から現在の場所への移動方向を角度で示す。0.0から359.9の値で、「0(北), 90(東), 180(南), 270(西)」となる。マイナス値が返ってきた場合は、正しく取得されていない。@property(readonly, nonatomic) CLLocationDirection course __OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_2_2); // 定義 typedef double CLLocationDirection;
サンプル
ソースコードはGitHubにて公開しています。iOS-SampleCodes/CoreLocationSample-CurrentLocation - GitHub
大幅変更位置情報サービスでは、アプリが終了しても位置情報サービスを利用できることが売りなため、当然ですがアプリが終了しても位置情報サービスを利用し続けます。そのため、大幅変更位置情報サービスを手動でオン・オフできるようにしなければ、アプリを削除するまでずっとバックグラウンドで動作し続けます。今回はスイッチを設けて、オフにすればバックグラウンドでの動作を止められるようになっています。
CoreLocationのサンプルを書いてらっしゃる方のプログラムを色々と探したのですが、残念ながら、いずれもバックグラウンドで動作させてしまうと、その後はアプリを削除する以外に位置情報サービスを無効にする方法がありませんでした。
位置情報は、やはり地図が無いと分かりづらいですが、その分単純なソースとなっています。
スクリーンショットの時間がバラバラになってしまいました。一番の問題はテストのしづらさでしょうか。いくつか試した結果、今回のサンプルの位置情報の精度は、標準アプリの「リマインド」と同じくらいの精度となっています。
0 コメント:
コメントを投稿