自宅Wi-Fiお知らせアプリ開発【第三回】 Beacon 探索機能編 その2

開発部のなーさんです。
この記事のために何度もアプリの試作を繰り返した結果、最近、家でもオフィスでもWi-Fi接続の通知がやたらと届くようになってしまいました。いらないアプリを整理しないと(汗
さて、普段はWEB系の開発をしている私が「家庭で使える」をテーマに突如はじめた、自社製品を使ったスマホアプリ作成チャレンジブログ、第3回は、いよいよ自宅Wi-Fiお知らせアプリのiBeacon探索機能の実装を行っていきたいと思います。

下記は2018年10月現在の情報で執筆しています。
文中、画像中で使用されている商標等はそれぞれ企業、団体に帰属します。
コンセプト実証用のソースコードのため簡略化等された箇所があります。

前回の要点

iBeacon探索のためのCoreLocation.frameworkの情報を整理しました。
自宅Wi-Fiお知らせアプリ開発【第二回】 Beacon 探索機能編
自宅Wi-Fiお知らせアプリ開発【第一回】準備編
今回開発に使用した環境を記載します。
●ハードウェア
iMac2017(macOS 10.14)
iPad Air2 (iOS12.0.1)SIMなし
BLEAD-B 1台
Wi-Fiルータ 1台
●ソフトウェア
Xcode 10.0 + Swift4.2
Apple developer Programアカウント ※HotSpotの許可をONにするために必須です。
プロジェクト形態はSingle View Appで、アプリ名は「Wi-FiNotify」としました。パッケージ名は任意のものを記入してください。
自宅Wi-Fiお知らせアプリの完成イメージです。

ソースコードの実装

前回、iBeaconの探索を行うCoreLocation.frameworkについて整理しました。では、実際にコーディングしていきたいと思います。簡略化のため、ViewController.swiftに直接記述していきます。
実行中の情報は、Print文を使ってコンソールに出力するようにします。
プロジェクトやInfo.plistなどの設定については、第1回の「プロジェクトの設定」をご覧ください。

各種定義の追加

ViewController.swiftの先頭に、CoreLocation.frameworkをインポートします。またViewControllerでCLLocationManagerDelegateを受けられるようにします。
ソースコードの先頭部分を以下のようにしてください。

デリゲートの追加

ViewControllerクラスにCLLocationManagerDelegateの各デリゲートをまとめて追加してしまいます。iBeacon探索にはたくさんのデリゲートを使うので、それぞれの役割は確認しておいてください。

CLLocationManagerクラスとCLBeaconRegionクラスをメンバ変数として宣言しています。
宣言のみとして、インスタンスはあとで生成します。

Private メソッドの定義

次にViewControllerクラスにPrivateメソッドを追加していきます。上で記述したデリゲートの下にまとめて記述するとよいでしょう。
下記の機能を持ったメソッドを定義します。

名前機能
checkForLocationServices()位置情報の許可などを確認
startMonitoringBeacons()探索するCLBeaconRegionのインスタンス生成と探索開始

●checkForLocationServices()
ここでは探索を開始する前にデバイスや許可の状態チェックをまとめて行っています。サンプルでは下記の2種類について行なっています。

  • 探索(モニタリング)に対応しているかどうか
  • OSの位置情報がオンになっているか

実用性を考慮する場合は、Bluetoothのオンオフの状態チェック等も行なった方がよいでしょう。

●startMonitoringBeacons()
今回は1台のiBeaconのみ検知できればよいので、余計なiBeaconを検知しないようにUUID/Major/Minorの全てを指定して、ピンポイントで探索(モニタリング)するようにしました。設定値は画面から設定しないため、躊躇なくソースコードに埋め込んでいます。
beaconIDは、検知されたRegionを識別するために使うIDです。複数のRegionを探索する場合は、文字列に番号を付加して管理するとよさそうです。
念のため、メソッドの冒頭で、既にRegionが生成されていたら無視する記述を入れてみましたが、いくつかソースコードを書いて試したところ、そこまで気を使わなくてもなんとなく「よしなに」機能してくれるようです。CPU負荷やメモリ使用量がどんどん増えていかなければいいと思います。
さて準備ができたら、生成したCLBeaconRegionについて探索を開始します。このメソッドは、didChangeAuthorizationデリゲートから呼び出される想定なので、引数としてCLLocationManagerのインスタンスを渡してもらって利用しています。

メソッドviewDidLoad()へのコード追加

viewDidLoad()では下記を行っています。

  • OSで位置情報がオンになっているか等の確認
  • CLLocationManagerのインスタンス化および各種設定
  • アプリの位置情報の利用を許可するかの確認

アプリの位置情報の利用を許可するかをユーザーが決められるようになっており、初回起動時には必ずダイアログを出して利用許可を得なければなりません。ダイアログでは「常に許可」を選択してください。

各デリゲートへのコード追加

デリゲート内の処理を記述していきます。
●didChangeAuthorization
アプリ起動中であっても、ユーザーはOSの設定画面から位置情報の許可を変更することができます。起動中に設定が変更されたときは、次にアプリがフォアグラウンドになったときにこのデリゲートが呼ばれます。
位置情報が「常に許可(CLAuthorizationStatus.authorizedAlways)」のときのみ、ビーコンの探索を行うようにしてあります。

●didStartMonitoringFor
Regionの探索を開始した時点で既にBeaconが近くにあった場合、自動ではRegionの検知が発生しません。そこで探索開始時には毎回、このデリゲートからdidDetermineStateデリゲートを明示的に呼び出して、状態確認を行なっています。

●didEnterRegion
●didExitRegion
●didDetermineState
didStartMonitoringForデリゲートから明示的にdidDetermineStateデリゲートを呼び出した場合、レンジング開始処理もdidDetermineStateで行う必要があります。しかし、ここで気になる点が!
公式ドキュメントでは、didEnterRegionデリゲートでレンジングを開始しています。これを取り入れるとレンジング開始処理がdidEnterRegionの後のdidDetermineStateでも呼ばれることになり、なーさんは気分的にすっきりしませんでした。
そこで今回は、めんどくさいのでコードをなるべくまとめるため、レンジングの開始・終了ともdidDetermineStateに記述してしまいました。
そうは言っても、やっぱりdidEnterRegion/didExitRegionにもレンジング開始・終了を記述したい!という場合も考慮して、Regionがレンジング中かどうかをチェックするコードも記述しています。

●didRangeBeacons
レンジング中のiBeaconの情報を取得します。
今回はBeaconが1台なので不要ですが、複数台でも対応できるよう、For文で1台ずつ情報を取得できるようにしています。複数台のBeaconから特定のMajor/Minorのみを処理したいときはif文などで抽出してください。
次回は、ここからWi-Fi接続ダイアログの表示を呼び出します。

●rangingBeaconsDidFailFor
レンジング中のエラー処理は、エラー内容を表示するだけにしてあります。必要に応じてレンジングやモニタリングを中止などの処理を記述してください。

動作確認

シミュレータでは動作しないので、スマホ実機にインストールしてください。
下記はアプリ画面とログのXcodeコンソール出力例です。あまりにも寂しい画面だったので、アプリ名のラベルだけはもう貼り付けてしまいました。ログは、アプリを起動してiBeaconを検知してから検知終了するまでのものとなります。1回目の起動では、最初に位置情報の許可処理も行われます。
もし動作しないようであれば下記を確認してみてください。

  • OSの設定画面のBluetooth
  • Info.plistの追記内容
  • BeaconのUUID/Major/Minor & Regionの探索条件
  • プロジェクトのクリーン & ビルド

【アプリの画面(アプリ名のラベルを追加したもの)】

【Beaconを検知したときのログ出力例】

INFO: 探索に対応しています
INFO: 位置情報はONです
Delegate: didChangeAuthorization
INFO: 位置情報 always
INFO: Regionの探索を開始しました
Delegate: didStartMonitoringFor
Delegate: didDetermineState
INFO : didDetermineState_unknown, id= jp.co.houwa-js.test.blog
Delegate: didDetermineState
Delegate: didEnterRegion
Delegate: didDetermineState
INFO : didDetermineState_inside, id= jp.co.houwa-js.test.blog
Delegate: didRangeBeacons
INFO: レンジング中のビーコン数 = 1
INFO: maj:min 100 : 100 , rssi = -78
INFO: Wi-Fiダイアログ表示回数 = 0
Delegate: didRangeBeacons
INFO: レンジング中のビーコン数 = 1
INFO: maj:min 100 : 100 , rssi = -77
INFO: Wi-Fiダイアログ表示回数 = 0
・・・中略
Delegate: didRangeBeacons
INFO: レンジング中のビーコン数 = 0
Delegate: didRangeBeacons
INFO: レンジング中のビーコン数 = 0
Delegate: didExitRegion
Delegate: didDetermineState
INFO : didDetermineState_outside, id= jp.co.houwa-js.test.blog

次回の予定

今回はCoreLocationを使ったiBeacon探索機能を実装してみました。
次回は一番大事なWi-Fi接続お知らせ機能についてです。
以上、ご質問・ご指摘等ありましたら、弊社までお気軽にお問い合わせください。

自宅Wi-Fiお知らせアプリ開発【第四回】 Wi-Fiお知らせ機能編

関連記事

  1. 知識ゼロで Unity をはじめてみた【その5 -Android 端末…

  2. Flutter と Product と Replace と

  3. ビーコンを使ったソリューションの紹介

  4. アジャイル ~壁を乗り越える~【第1回 導入編】

  5. ASP.NET MVC で Wijmo を使う – 6

  6. Bluetooth SIGブログで紹介された「7つの活用方法」の詳細を…