Xamarin.iOSでBeaconを観測する

Xamarin.Formsを用いて、あるスマホアプリを作るのに(まだリリース前)、Beaconを用いたので、まとめてみたいと思います。
Swiftでの実装例は沢山あったんですけど、Xamarinでの実装例に関してあまり記事がなく少しばかり苦労したので、そういう方にとって理解しやすいような記事になっていれば幸いです。
今回はとりあえずiOSに関して書きます。

#Beaconとは
Beaconとは、低消費電力の近距離無線技術「Bluetooth Low Energy」（BLE）を利用した位置特定技術、また、その技術を利用したデバイスのこと。
BLE形式のビーコンは何秒かに一回など断続的に信号を発信する形式であり、信号をだしっぱなしにするよりも電池の消費が抑えられ長期間使える、といった特徴があります。

#GPSと何が違うの？
GPSとBeaconの最も大きな違いは、その発信源です。GPSは大気圏外に浮かぶ人工衛星からの情報を受け取りますが、Beaconは建物内や屋外の一地点に置いた発信源からの信号を受信します。

よって、GPSは電波の届かないところでは情報を受け取れないことがありますが、Beaconにおいてその心配はありません。

#Beaconを使って何ができるの？？
##Beaconでできること１[Monitoring]
Beaconを受信できる範囲内に入ると、通知を受け取ることができます。
##Beaconでできること2[Ranging]
Beaconを発信している機器との距離を把握することができます。

#Beaconの活用事例
八景島シーパラダイス
https://www.itmedia.co.jp/makoto/articles/1408/18/news085.html
>来場者は事前に専用アプリ「beaconnect（ビーコネクト）」をスマートフォンへインストールしておくことで、島内4つの水族館「アクアミュージアム」「うみファーム」「ドルフィンファンタジー」「ふれあいラグーン」など、来場者が今いる場所に合わせた水族館内の生きもの情報・豆知識やイベント情報を自動的に配信する。

アメリカの「MLB（メジャリーグ・ベースボール）」
https://www.mlb.com/apps/ballpark
>アメリカの「MLB（メジャリーグ・ベースボール）」では、MLB.com Ballparkという専用アプリがあります。20以上のスタジアムに各100個のビーコン端末を設置し、取得したチケット情報を元に座席まで案内してくれたり、屋台のおすすめ商品を教えてくれたりするのです。

GINZA SIX 
https://medium.com/tigerspike-tokyo/ginza-six-%E9%A4%A8%E5%86%85180%E3%82%AB%E6%89%80%E3%81%AB-beacon-%E3%82%92%E8%A8%AD%E7%BD%AE%E5%B0%8E%E5%85%A5%E3%81%97%E3%81%BE%E3%81%97%E3%81%9F-%E3%81%9D%E3%81%AE%EF%BC%92-a1c076808690
>アプリをお使いの客様に、館内のどのフロアのどの辺りにいるかと、行きたいショップまでの最短ルートを表示します。例えば、5Fから4Fのショップへ行く場合、今いる場所から、エレベーター、エスカレーター、階段を使うルートの中から一番距離が短いルートを案内します。

#開発環境
- macOS Mojave バージョン10.14.4
- Visual Studio 2017
- Xamarin.Forms
- Xamarin.iOS

#Beaconの機能
Beaconの機能は大きく分けて２つあります。

- Monitoring
- Ranging

の２つです。
この２つに関して、具体的にどのようなことをするのか次に示します。

### Monitoringとは
ビーコン領域の監視です。
設定したリージョンにユーザが入ったり出たりしたときに通知を受け取る仕組みです。リージョン監視はバックグランドでも動作するので、お店に入ったときにポイントカードやクーポンの通知を表示するといったアプリを簡単に実装することができます。

### Rangingとは
Rangingは、設定されたリージョンに入ったiBeaconデバイスのUUID/major/minorといった情報と、Bluetooth信号強度や、およその距離が取得できます。Rangingが有効になっていると、１秒ごとに通知が来ます。
ちなみにRangingはMonitoringhと違って、バックグランドでの動作がサポートされていません。

#Monitoringの実装
canGetNotificationで、既にBeacon領域にEnterしてるかどうかチェックしないままやると、同時に2つ通知が送られてきてしまう事がよくあったので、bool変数を定義しました。

RegionLeftは、実際にBeaconを感知しなくなってから、約40秒ほど経ってから呼び出されました。これは、逆にLeftしていないのにそう認識してしまうということを避けるために、そういう仕組みになっているらしいです。

```AppDelegate.cs
using System;
using Foundation;
using UIKit;
using Beacon.iOS.Model;
using System.Diagnostics;
using WindowsAzure.Messaging;
using CoreLocation;

namespace Beacon.iOS
{
    // The UIApplicationDelegate for the application. This class is responsible for launching the 
    // User Interface of the application, as well as listening (and optionally responding) to 
    // application events from iOS.
    [Register("AppDelegate")]
    public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
    {

        #region fields
        CLLocationManager locationMgr;
        CLBeaconRegion region;
        private static bool canGetNotification;
        #endregion

        #region methods
        //アプリが起動完了した時に、APNS サーバにアプリを登録する
        public override bool FinishedLaunching(UIApplication app, NSDictionary options)
        {
            Console.WriteLine("FinishedLaunching called");
            global::Xamarin.Forms.Forms.Init();
            //本来ここら辺にRemote Notificationsの設定が書かれていると思うので、その下に以下を記述
            canGetNotification = true;//静的変数
            this.locationMgr = new CLLocationManager();//CLLocationManagerをインスタンス化
            this.locationMgr.AuthorizationChanged += LocationManagerAuthorizationChanged;//Beaconの設定を追加
            this.locationMgr.RegionEntered += LocationManagerRegionEnter;//メソッド①を追加
            this.locationMgr.RegionLeft += LocationMangagerRegionLeft;//メソッド②を追加
            return base.FinishedLaunching(app, options);//元々あるやつです
        }
        
        //①Regionに入った時に呼び出されるメソッド
        void LocationManagerRegionEnter(object sender, CLRegionEventArgs e)
        {
            if(canGetNotification)
            {
                //Alertを作成
                var notification = new UILocalNotification();
                notification.AlertBody = "近くにいるBeaconを感知しました！";
                UIApplication.SharedApplication.PresentLocalNotificationNow(notification);
                canGetNotification = false;
            }
        }

        //②Regionから外に出た時に呼び出されるメソッド
        void LocationMangagerRegionLeft(object sender, CLRegionEventArgs e)
        {
            if (!canGetNotification)
            {
                Debug.WriteLine("Regionから外に出たよ");
                canGetNotification = true;
            }
        }

        //Beaconの設定
        void LocationManagerAuthorizationChanged(object sender, CLAuthorizationChangedEventArgs e)
        {
            if(e.Status == CLAuthorizationStatus.AuthorizedAlways)//位置情報サービスが常にOnになっていれば
            {
                this.region = new CLBeaconRegion(new NSUuid("UUIDを入力"), "好きな名前");
                this.region.NotifyOnEntry = true;//領域に入った事を監視する
                this.region.NotifyOnExit = true;//領域から出たことを監視する
                this.locationMgr.StartMonitoring(this.region);//監視スタート
            }
        }

```

#Rangingの実装
importやクラスの定義は省略します。

```iBeaconRanging.cs
        #region fields
        CLBeaconRegion beaconRegion;
        CLBeacon clbeacon;
        private string status = "";
        private int major;
        private int minor;
        #endregion

        public void GetStatusOfMonitoring()
        {
            string uuid = "ここにはUUIDを入れる";
            NSUuid Uuid = new NSUuid(uuid);//Uuidを作成
            beaconRegion = new CLBeaconRegion(Uuid, uuid);
            locationMgr.StartRangingBeacons(beaconRegion);//Ranging開始
            locationMgr.DidRangeBeacons += (object sender, CLRegionBeaconsRangedEventArgs e) =>
            {
                if (e.Beacons.Length > 0)
                {
                    clbeacon = e.Beacons[0];//ここはどんどんスタックされていかないのか
                    this.major = (int)clbeacon.Major;
                    this.minor = (int)clbeacon.Minor;
                    this.status = clbeacon.Proximity.ToString();
                    switch (clbeacon.Proximity)
                    {
                        //観測できている時は、距離を表示
                        case CLProximity.Immediate:
                        case CLProximity.Near:
                        case CLProximity.Far:
                            Debug.WriteLine("現在約" + CutNumber(clbeacon.Accuracy) + "m離れたところにいます");//距離を表示
                            break;
                        //観測範囲内から消えた場合
                        case CLProximity.Unknown:
                            Debug.WriteLine("Unknown");
                            break;
                    }
                }
            };
        }
```

#最後に
既にたくさんのBeaconを使ったアプリがあるので、今内容な使い方をするBeaconアプリを作ってみたいと思います。
Android, Swift版についても時間がある時に書こうと思います。

#参考
たくさんのページを参考にしましたが、Microsoftのdocsをよく見ました。