iOS + Swift4 + CoreLocation + Geofence 実装

# iOS + Swift4 + CoreLocation + Geofence

## 環境

- OS: iOS 11
- IDE: Xcode 9.0.1
- Languege: Swift4

## 概要

iOSアプリで指定領域に出入りした時を検知する方法の一つにジオフェンスを設定してモニタリングする方法があります。今回はその基本実装方法とXcodeでの移動シミュレーションの方法を記載したいと思います。サンプルでは六本木から東京タワーまでの移動をシミュレーションをして東京タワーに1km内に出入りした時を検知するものを記載。

## ソースコード
```swift:ViewController.swift
import UIKit
import CoreLocation

class ViewController: UIViewController, CLLocationManagerDelegate {
	var locationManager: CLLocationManager!
	
	override func viewDidLoad(){
		super.viewDidLoad()
		
		// LocationManagerのインスタンスの生成
		locationManager = CLLocationManager()
		
		// LocationManagerの位置情報変更などで呼ばれるfunctionを自身で受けるように設定
		locationManager.delegate = self
		
		// 位置情報取得をユーザーに認証してもらう
		locationManager.requestAlwaysAuthorization()
		
		// モニタリング開始：このファンクションは適宜ボタンアクションなどから呼ぶ様にする。
		self.startGeofenceMonitering()
	}
	
	// MARK: - Monitering function
	
	func startGeofenceMonitering() {
	
		// 位置情報の取得開始
		locationManager.startUpdatingLocation()
		
		// モニタリングしたい場所の緯度経度を設定
		let moniteringCordinate = CLLocationCoordinate2DMake(35.658581, 139.745433) // 東京タワーの緯度経度
		
		// モニタリングしたい領域を作成
		let moniteringRegion = CLCircularRegion.init(center: moniteringCordinate, radius: 1000.0, identifier: "TokyoTower")
		
		// モニタリング開始
		locationManager.startMonitoring(for: moniteringRegion)
	}
	
	// MARK: - CLocationManagerDelegate
	
	// 位置情報取得認可
	func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationsStatus) {
		switch status {
		case .notDetermined:
			print("ユーザー認証未選択")
			break
		case .denied:
			print("ユーザーが位置情報取得を拒否しています。")
			//位置情報取得を促す処理を追記
			break
		case .restricted:
			print("位置情報サービスを利用できません")
			break
		case .authorizedWhenInUse:
			print("アプリケーション起動時のみ、位置情報の取得を許可されています。")
			break
		case .authorizedAlways:
			print("このアプリケーションは常時、位置情報の取得を許可されています。")
			break
		}
	}
	
	// ジオフェンスモニタリング
	
	// モニタリング開始成功時に呼ばれる
	func locationManager(_ manager: CLLocationManager, didStartMoniteringFor region: CLRegion) {
		print("モニタリング開始")
	}
	
	
	// モニタリングに失敗したときに呼ばれる
    func locationManager(_ manager: CLLocationManager, monitoringDidFailFor region: CLRegion?, withError error: Error) {
        print("モニタリングに失敗しました。")
    }
    
    // ジオフェンス領域に侵入時に呼ばれる
    func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
        print("設定したジオフェンスに入りました。")
    }
    
    // ジオフェンス領域から出たときに呼ばれる
    func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
        print("設定したジオフェンスから出ました。")
    }
    
    // ジオフェンスの情報が取得できないときに呼ばれる
    func locationManager(_ manager: CLLocationManager, didFailWithError error: NSError) {
        print("モニタリングエラーです。")
    }
}
```

## ジオフェンス移動をXcodeでシミュレーションする方法

ジオフェンスの位置移動でジオフェンスに入ったときをシミュレーションするためにGPX形式の位置移動ファイルを作成してXcodeに入れることでSimulatorで確認することができます。以下の参考記事に詳しく書いているので要参考。ここでは簡易的な手順のみ記載します。

参考記事：[iOSでジオフェンスに触れる②](https://intheweb.io/geofence-ios-2/)

### googleマップで移動するルートを作成

webブラウザでgoogle mapを開き、六本木から東京タワーまでのルートを作成

### GPXファイルの生成

https://mapstogpx.com

を開き

1. 上記で作成したgoogle mapのルートURLをコピー
2. ``Paste your Google Maps link here..``にURLをペースト
3. ``Track Points``をチェック
4. ``Let's Go``をクリック

上記でGPXファイルがダウンロードされます。

### GPXファイルの編集

1. ダウンロードしたGPXファイルをテキストエディタで開く
2. ``<wpt>``から``</wpt>``で内包する子要素と``wpt``タグも含めて削除
3. ``<trk>``と``</trk>``タグを削除
4. ``<trkseg>``と``</trkseg>``タグを削除
5. ``<trkpt>``タグを``<wpt>``タグに変更
6. ``</trkpt>``タグを``</wpt>``タグに変更

上記で編集した六本木から東京タワーのGPXファイルは以下のような感じになります。

```xml:*.gpx
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<gpx xmlns="http://www.topografix.com/GPX/1/1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" creator="mapstogpx.com" version="1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd">
  <metadata>
    <link href="http://www.mapstogpx.com">
      <text>Sverrir Sigmundarson</text>
    </link>
    <!--desc>Map data ©2018 Google, ZENRIN</desc-->
    <!--copyright author="Google Inc">
    	<year>2018</year>
    	<license>https://developers.google.com/maps/terms</license>
    </copyright-->
    <!--url>https://www.google.co.uk/maps/dir/六本木、〒106-0032 東京都港区/〒105-0011 東京都港区芝公園４丁目２−８ 東京タワー/@35.662112,139.7324352,16z/data=!3m2!4b1!5s0x60188bbd90bf26cf:0x4ceb5b05598646d0!4m14!4m13!1m5!1m1!1s0x60188b9d3c1c9187:0x48f9c248e9169cfe!2m2!1d139.729426!2d35.6641222!1m5!1m1!1s0x60188bbd9009ec09:0x481a93f0d2a409dd!2m2!1d139.7454329!2d35.6585805!3e0?hl=en</url-->
    <time>2018-01-26T13:19:20Z</time>
  </metadata>

  <name>六本木、〒106-0032 東京都港区 to 〒105-0011 東京都港区芝公園４丁目２−８ 東京タワー</name>
  <number>1</number>

  <wpt lat="35.6641353" lon="139.7293255">
    <name>TP001</name>
  </wpt>
  <wpt lat="35.66397" lon="139.72929">
    <name>TP002</name>
  </wpt>
  <wpt lat="35.66361" lon="139.72921">
    <name>TP003</name>
  </wpt>
  <wpt lat="35.66335" lon="139.72915">
    <name>TP004</name>
  </wpt>
  <wpt lat="35.66299" lon="139.72894">
    <name>TP005</name>
  </wpt>
  <wpt lat="35.66315" lon="139.72841">
    <name>TP006</name>
  </wpt>
  <wpt lat="35.6632" lon="139.72824">
    <name>TP007</name>
  </wpt>
  <wpt lat="35.6632018" lon="139.7282364">
    <name>TP008</name>
  </wpt>
  <wpt lat="35.66266" lon="139.72821">
    <name>TP009</name>
  </wpt>
  <wpt lat="35.6626557" lon="139.7282137">
    <name>TP010</name>
  </wpt>
  <wpt lat="35.6622" lon="139.72938">
    <name>TP011</name>
  </wpt>
  <wpt lat="35.66212" lon="139.7296">
    <name>TP012</name>
  </wpt>
  <wpt lat="35.6621186" lon="139.7296">
    <name>TP013</name>
  </wpt>
  <wpt lat="35.66274" lon="139.73096">
    <name>TP014</name>
  </wpt>
  <wpt lat="35.6627391" lon="139.7309634">
    <name>TP015</name>
  </wpt>
  <wpt lat="35.66275" lon="139.7311">
    <name>TP016</name>
  </wpt>
  <wpt lat="35.66276" lon="139.73112">
    <name>TP017</name>
  </wpt>
  <wpt lat="35.66279" lon="139.73127">
    <name>TP018</name>
  </wpt>
  <wpt lat="35.66282" lon="139.73138">
    <name>TP019</name>
  </wpt>
  <wpt lat="35.66325" lon="139.7323">
    <name>TP020</name>
  </wpt>
  <wpt lat="35.6632483" lon="139.7323041">
    <name>TP021</name>
  </wpt>
  <wpt lat="35.66315" lon="139.73236">
    <name>TP022</name>
  </wpt>
  <wpt lat="35.66279" lon="139.73319">
    <name>TP023</name>
  </wpt>
  <wpt lat="35.66266" lon="139.73346">
    <name>TP024</name>
  </wpt>
  <wpt lat="35.66243" lon="139.73393">
    <name>TP025</name>
  </wpt>
  <wpt lat="35.66229" lon="139.73425">
    <name>TP026</name>
  </wpt>
  <wpt lat="35.66206" lon="139.73501">
    <name>TP027</name>
  </wpt>
  <wpt lat="35.66175" lon="139.73608">
    <name>TP028</name>
  </wpt>
  <wpt lat="35.66164" lon="139.73644">
    <name>TP029</name>
  </wpt>
  <wpt lat="35.66158" lon="139.73662">
    <name>TP030</name>
  </wpt>
  <wpt lat="35.66146" lon="139.73701">
    <name>TP031</name>
  </wpt>
  <wpt lat="35.66144" lon="139.73708">
    <name>TP032</name>
  </wpt>
  <wpt lat="35.66123" lon="139.73777">
    <name>TP033</name>
  </wpt>
  <wpt lat="35.66116" lon="139.738">
    <name>TP034</name>
  </wpt>
  <wpt lat="35.6609" lon="139.73869">
    <name>TP035</name>
  </wpt>
  <wpt lat="35.6606" lon="139.73941">
    <name>TP036</name>
  </wpt>
  <wpt lat="35.66011" lon="139.74062">
    <name>TP037</name>
  </wpt>
  <wpt lat="35.65986" lon="139.74126">
    <name>TP038</name>
  </wpt>
  <wpt lat="35.65956" lon="139.74194">
    <name>TP039</name>
  </wpt>
  <wpt lat="35.65952" lon="139.74213">
    <name>TP040</name>
  </wpt>
  <wpt lat="35.65951" lon="139.7423">
    <name>TP041</name>
  </wpt>
  <wpt lat="35.65952" lon="139.74245">
    <name>TP042</name>
  </wpt>
  <wpt lat="35.65954" lon="139.74254">
    <name>TP043</name>
  </wpt>
  <wpt lat="35.65959" lon="139.74271">
    <name>TP044</name>
  </wpt>
  <wpt lat="35.6597" lon="139.74294">
    <name>TP045</name>
  </wpt>
  <wpt lat="35.6597026" lon="139.7429397">
    <name>TP046</name>
  </wpt>
  <wpt lat="35.65975" lon="139.74304">
    <name>TP047</name>
  </wpt>
  <wpt lat="35.65977" lon="139.7431">
    <name>TP048</name>
  </wpt>
  <wpt lat="35.65978" lon="139.74312">
    <name>TP049</name>
  </wpt>
  <wpt lat="35.65981" lon="139.74327">
    <name>TP050</name>
  </wpt>
  <wpt lat="35.65983" lon="139.74341">
    <name>TP051</name>
  </wpt>
  <wpt lat="35.6598" lon="139.7437">
    <name>TP052</name>
  </wpt>
  <wpt lat="35.65978" lon="139.74393">
    <name>TP053</name>
  </wpt>
  <wpt lat="35.65977" lon="139.74401">
    <name>TP054</name>
  </wpt>
  <wpt lat="35.65974" lon="139.7441">
    <name>TP055</name>
  </wpt>
  <wpt lat="35.65968" lon="139.74425">
    <name>TP056</name>
  </wpt>
  <wpt lat="35.65937" lon="139.74495">
    <name>TP057</name>
  </wpt>
  <wpt lat="35.6593694" lon="139.7449509">
    <name>TP058</name>
  </wpt>
  <wpt lat="35.65875" lon="139.74472">
    <name>TP059</name>
  </wpt>
  <wpt lat="35.65836" lon="139.74506">
    <name>TP060</name>
  </wpt>
  <wpt lat="35.6583649" lon="139.7450629">
    <name>TP061</name>
  </wpt>
</gpx>
```

### GPXファイルの取り込み

Xcodeのプロジェクトに上記ファイルを読み込みます。

### シミュレーション方法

XcodeでRunさせるとウィンドウ下部に``↗``のようなアイコンが有るので、そこをクリックして先程プロジェクトに読み込んだファイル名を選択することで、約一秒毎に六本木から東京タワーまでの位置移動が始まります。

東京タワー（終点）の緯度経度まで行くと、終了するのではなく再度六本木（始点）に移動して東京タワー（終点）への移動が始まります。始点→終点がループするようです。


### didEnterRegionとdidExitRegionが呼ばれないときの対処法

locationManagerの``didEnterRegion``と``didExitRegion``が最初呼ばれなかったが、位置情報取得の認可設定をする``info.plist``の設定で``NSLocationAlwaysAndWhenInUseUsageDescription``と``NSLocationWhenInUseUsageDescription``の２つを設定すると呼ばれるようになった。

最初は``NSLocationWhenInUseUsageDescription``だけしか設定していなかったのでその場合は上記が呼ばれない模様

```xml:info.plist
・・・中略・・・
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>位置情報を常に許可する使用目的を記述</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>位置情報をアプリ起動時のみ許可する使用目的を記述</string>
・・・中略・・・
```


参考 stackoverflow [didEnterRegion, didExitRegion not being called
](https://stackoverflow.com/questions/37498438/didenterregion-didexitregion-not-being-called)

## まとめ

この機能を使って他のプロジェクトでGoogle HomeやAmazon Alexaのアプリを作っているので、家族の位置情報をスマートスピーカーが答えてくれるものや、LINE Message APIなどと連携して帰宅時に家族にLINEメッセージを送信したりと言う部分も実装しようと考えています。（LINE Message API連携）