iOSのジオフェンス機能

iOSのジオフェンス機能を仕事で使う機会があり、いろいろ検証してみてわかったことがありましたので備忘録を兼ねて投稿しようと思います。

##ジオフェンスとは
特定範囲の仮想境界線のことで、例えば山手線の五反田駅から200mをジオフェンスとしてマップに表示するとこんな感じの円になります。
![kobito.1411191158.398662.png](https://qiita-image-store.s3.amazonaws.com/0/37373/0f1b7b6e-e602-f8ea-bd39-be670e116c95.png "kobito.1411191158.398662.png")


この範囲から出た・入ったを通知する機能が本投稿で説明するジオフェンス機能で、
iOSでは[CoreLocation FrameworkのstartMonitoringForRegion:](URL "https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/CLLocationManager/CLLocationManager.html#//apple_ref/occ/instm/CLLocationManager/startMonitoringForRegion:")を使って実装できます。


ジオフェンス使用中のアプリがある場合、ステータスバーには下記のように表示されます。
![kobito.1411191061.704975.png](https://qiita-image-store.s3.amazonaws.com/0/37373/d9df5d37-eb5b-5773-8a38-fed4cb860b47.png "kobito.1411191061.704975.png")


ちなみに、通常の位置情報取得/大幅な位置情報取得を使用中のアプリがある場合、ステータスバーには下記のように表示されます。
![kobito.1411191010.567537.png](https://qiita-image-store.s3.amazonaws.com/0/37373/8b543e44-6553-f6bf-7ff8-6a187a1ab657.png "kobito.1411191010.567537.png")




##できること
#### 指定領域に入った/出たがわかる。
 * 歩いて入ったときとかは結構正確（すぐに通知されて誤差は10m以内）に通知されると思ったのですが、電車で通過したときは通知されなかったり数分遅れてから通知が来たりします。出たの通知はもっと遅く感じています。Wifi-ONで検証していたので、Wifi-OFFだと精度は悪くなるかもしれません。

#### 指定領域は、中心点（緯度,経度）と半径で指定。
 * OSのよって得意な半径があるようで、iOS6以降だと1~400m。iOS5だと1~150m。

#### 通常の位置情報取得よりもバッテリー消耗を抑えられる。

#### 指定領域に入った/出たはアプリが終了していても通知される。その場合、アプリは再起動される。
 * iOS Versionにより挙動が異なる。

	| iOS Version    | 挙動                                                                                                    |
|:---------------|:----------------------------------------------------------------------------------------------------------------|
| 6系         |アプリケーションスイッチャーにいてもいなくても再起動される。|
| 7.0系       |アプリケーションスイッチャーにいない状態だと再起動されない|
| 7.1系       |iOS6系と同じで、アプリケーションスイッチャーにいてもいなくても再起動される。|
| 8系         |requestAlwaysAuthorization(iOS8 SDKで追加されたAPI)コールして、ユーザーが位置情報取得を常に許可するとアプリ再起動される。<br>常に許可するとアプリケーションスイッチャーにいてもいなくても再起動される。|
<br>


##制限
#### iOS4以降から利用可能
#### 登録できるジオフェンスは１アプリ最大２０個まで
 * そのため、これより多くのジオフェンスを設定したい場合はアプリ起動時などに現在地の近くにある対象領域２０個を判定してジオフェンスを再登録などの対応が必要。

#### 位置情報取得を”常に許可”にする必要がある(iOS8のみ)。

##実装方法

###位置情報取得を常に許可する(iOS8のみ)
###### *ソース
> LCLocationManager *locationManager = [CLLocationManager new];
> //requestAlwaysAuthorizationメソッドが存在しているとき（iOS8以上)のみ処理する
> if([locationManager respondsToSelector:@selector(requestAlwaysAuthorization)] ) {
>		[locationManager requestAlwaysAuthorization];//これで位置情報取得をアプリ使用していないときも許可するかを促すアラートが表示される。
>}
> 

###### *info.plist
![screenshot01.png](https://qiita-image-store.s3.amazonaws.com/0/37373/b14fe26d-7588-017f-eced-8ced018f534c.png "screenshot01.png")

###### *位置情報取得をアプリ使用していないときも許可するかを促すアラート
![screenshot2014101301.png](https://qiita-image-store.s3.amazonaws.com/0/37373/a670d9b4-5418-92cf-692b-7c2dd7184c9f.png "screenshot2014101301.png")
これを”許可”してもらう。

<br>
###領域観測の登録
> locationManager.delegate = self;//領域に入った/出たなどを検知するdelegateを登録。ここではとりあえずselfとします。
> CLRegion* region = [[CLCircularRegion alloc] initWithCenter:CLLocationCoordinate2DMake(35.6817708,139.7294325) radius:200. identifier:@"RoppongiHills"];//六本木ヒルズの半径200mで領域を作成
> [locationManager startMonitoringForRegion:region];//領域観測の開始。１回これをコールしたら、アプリ終了しても領域観測停止（stopMonitoringForRegion:）をコールするまでずっと登録されたままになります。

<br>
###領域観測開始の通知受信
登録した領域の観測が開始されたときにOSから通知されます。
だいたい、startMonitoringForRegion:コール後、0〜5秒くらいでコールされます。
> /**
>  *  登録した領域観測が開始されたときの処理
>  *
>  *  @param manager locaitonManger
>  *  @param region  対象の領域
>  */
> (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
>      //登録した領域観測が開始されたときの処理
> }

<br>
###領域に入った通知受信
登録した領域の入ったときにOSから通知されます。
上記記載済みですが、歩いて入ったときとかは結構正確（すぐに通知されて誤差は10m以内）に通知されると思ったのですが、電車で通過したときは通知されなかったり数分遅れてから通知が来たりします。

> /**
>  *  登録した領域に入ったときの処理
>  *
>  *  @param manager locaitonManger
>  *  @param region  対象の領域
>  */
> - (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
>     //登録した領域に入ったときの処理
> }

<br>
###領域から出た通知受信
登録した領域から出たときにOSから通知されます。
領域に入ったの通知に比べて、出たの通知は遅いと感じています。

> /**
>  *  登録した領域から出たときの処理
>  *
>  *  @param manager locaitonManger
>  *  @param region  対象の領域
>  */
> - (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
>     //登録した領域から出たときの処理
> }

<br>
###領域観測でエラー発生受信
例えば、２１個以上の領域観測の登録（startMonitoringForRegion:）を行うと、領域数オーバーとして、これがOSからコールされます。

> /**
>  * 領域観測でエラー発生時の処理（領域観測の登録に失敗したときなど）
>  *
>  *  @param manager locaitonManger
>  *  @param region  対象の領域
>  */
> - (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error {
>     //領域観測でエラー発生時の処理
> }

<br>
###領域観測の停止
> [locationManager stopMonitoringForRegion:登録した領域（CLRegionオブジェクト）];

例えば、登録した領域観測をすべて停止する場合は以下のようになります。
> 	for (CLRegion *region in _locationManager.monitoredRegions) {
>         [locationManager stopMonitoringForRegion:region];
>     }

<br>
###領域の入った/出たなどでアプリ再起動されたとき
アプリ起動時にコールされるapplication:didFinishLaunchingWithOptions:の引数launchOptionsにUIApplicationLaunchOptionsLocationKeyキーが入ってきます。

> -(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
>     // Override point for customization after application launch.
>     if([[launchOptions allKeys] containsObject:UIApplicationLaunchOptionsLocationKey]) {
>         //領域の入った/出たなどでアプリが起動された
>     }
>     return YES;
> }

アプリで大幅位置変更（CLLocationManagerのstartMonitoringSignificantLocationChanges）を使っていた場合も同じようにlaunchOptionsにUIApplicationLaunchOptionsLocationKeyキーが入ってきます。


##最後に
これ違うよ！や参考になったよ！とかありましたらコメント頂けると嬉しいですm(_ _)m
一応、GitHubにサンプルソースをアップしましたので参考になればと思います。
サンプルソースは登録領域（山手線の駅２０個）をマップ上にピンで表示して、領域に入った/出たところもマップ上に表示するアプリになっています。
<br>
[https://github.com/HIkaruSato/GeofenceMap](https://github.com/HIkaruSato/GeofenceMap)
![IMG_2815.PNG](https://qiita-image-store.s3.amazonaws.com/0/37373/f43ee280-716d-0eb5-73bb-5e59bfd86afc.png "IMG_2815.PNG")

<br>
参考：
https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/
http://www.neglectedpotential.com/2014/09/ios-8-location-services-psa/