Takahiro Octopress Blog

-1から始める情弱プログラミング

iBeacon完全攻略!?

| Comments

iBeaconを復習しよう!

本日はこれまでも何度か取り上げてきたiBeaconについて改めて復習してみたいと思います。
今回はiOS端末でのiBeaconの発信/受信に特化して書きます。

これまでの関連記事は以下です。

では、早速、まとめていきましょう。

iBeaconが利用可能な端末

  • OS: iOS7.0以上
  • 端末: iPhone4S以降, iPad(第3世代)以降, iPad mini以降, iPod touch(第5世代以降) ※もちろんiPad Airでも利用可能

iOSごとのiBeacon機能の差異

まずは動作面での差異について

  • iOS7.0.xの場合
    アプリをFG起動もしくはBG起動していないとiBeaconを検知することはできない
  • iOS7.1.x以降の場合
    アプリを起動していない(停止状態の)場合でもiBeaconを検知可能

次にプログラミング面での差異について

プログラミング面ではCentral側にのみ多少の差異があります。
iBeaconの検知には CoreLocation.framework を利用します。 CoreLocation.framework は位置情報サービスを利用するためのフレームワークですが、iBeaconの領域監視メソッドが組み込まれています。
もともと、ジオフェンスの領域監視メソッドが組み込まれていますので、それと同等に扱いたいというApple側の意図が見えます。

具体的な差異について説明します。
位置情報サービスの利用許可メソッドをiOS8から組み込む必要があります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#import "ViewController.h"

@interface ViewController ()

@property (strong, nonatomic) CLLocationManager *lm;
@property (strong, nonatomic) NSUUID *proximityUUID;
@property (strong, nonatomic) CLBeaconRegion *beaconRegion;
@property (strong, nonatomic) CLBeacon *nearestBeacon;
@property (strong, nonatomic) NSString *str;

@end

@implementation ViewController

- (void)viewDidLoad
{
  [super viewDidLoad];

  if ([CLLocationManager isMonitoringAvailableForClass:[CLCircularRegion class]]) {
      // iOS7.x以降の端末
      // CLLocationManagerの初期化
      self.lm = [[CLLocationManager alloc] init];
      self.lm.delegate = self;

      // UUIDの設定
      self.proximityUUID = [[NSUUID alloc] initWithUUIDString:@"8D4DB809-032F-4771-96F3-99BD5C25F924"];
      // Bundle Identifierの取得
      NSBundle *bundle = [NSBundle mainBundle];
      NSString *bid = [bundle bundleIdentifier];
      self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID: self.proximityUUID identifier: bid];

      if ([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) {
          // iOS8以上の場合
          // 位置情報サービスを常に許可させて良いかどうかを尋ねるためのメソッド
          [self.lm requestAlwaysAuthorization];
      } else {
          // iOS8未満の場合
          // iBeacon領域の監視を開始
          [self.lm startMonitoringForRegion: self.beaconRegion];
      }
  } else {
      // iOS6.x以前の端末
      UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"確認" message:@"iBeacon機能を利用できない端末です。" delegate: nil cancelButtonTitle: nil otherButtonTitles:@"OK", nil];
      // アラートを表示
      [alert show];
  }
}

// ユーザの位置情報の許可状態を確認するメソッド
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
  if (status == kCLAuthorizationStatusAuthorizedAlways) {
      // ユーザが位置情報の使用を常に許可している場合
      // iBeacon領域の監視を開始
      [self.lm startMonitoringForRegion: self.beaconRegion];
  } else {
      // その他の場合
      // 設定画面に遷移
      NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
      [[UIApplication sharedApplication] openURL:url];
  }
}

@end

iOS7.xではstartMonitoringForRegionメソッドを実行した段階でdidChangeAuthorizationStatusメソッドが呼ばれます。
iOS8では、requestAlwaysAuthorizationメソッドを実行(位置情報サービスを常に許可する場合のメソッド)しなければ、didChangeAuthorizationStatusメソッドが呼ばれないため、startMonitoringForRegionメソッドの実行タイミングを変える必要が出てきました。

iBeacon関連メソッドの実行順について(Centralの場合)

先ほどiBeaconの検知は CoreLocation.framework 内のメソッドとして組み込まれていると説明しました。では、他にどんなメソッドがあるのでしょうか。

1
2
3
// 領域監視が開始された後に呼び出される処理
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
}
1
2
3
// 領域監視に失敗した場合に呼び出される処理
- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error {
}
1
2
3
// 監視領域に入った場合に呼び出される処理
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
}
1
2
3
// 監視領域から出た場合に呼び出される処理
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
}
1
2
3
// 監視領域に対する状態が変化した場合に呼び出される処理
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region {
}
1
2
3
// iBeacon信号をレンジング検知した場合に呼び出される処理
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region {
}

上記のメソッドを使えば十分なアプリが開発できるはずです。
さて、ここで注意しておくべきなのは、startRangingBeaconsInRegionメソッドをどのタイミングで実行するかということです。

基本的には、 監視領域に入ったタイミングstartRangingBeaconsInRegionメソッドを実行するのが普通かもしれません。
しかし、この場合は注意が必要です。
なぜなら、監視領域内でアプリを落として、再度起動した場合、didEnterRegionメソッドが実行されないからです。つまり、既に監視領域に入っている場合は 監視領域に侵入したと見なされない ということです。
よって、こういったケースがアプリの利用に打撃を与えるのであれば、 監視領域に入ったタイミング のみにstartRangingBeaconsInRegionメソッドを置くわけにはいかないことになります。
その場合は、didDetermineStateメソッドで既に監視領域内にいる場合(stateCLRegionStateInsideの場合)にstartRangingBeaconsInRegionメソッドを実行するようにしましょう。

Peripheralの注意点

続いて、PeripheralとしてiOS端末を使う場合の注意点についてお話しておきます。
iOS端末ではアプリをFG起動している間のみiBeacon信号の発信が可能です。
アプリがBG起動になった場合、iBeacon信号の発信が止まってしまうため、数秒後にCentral側のiOS端末でdidExitRegionメソッドが実行されます。
なので、iOS端末でPeripheralの役割を担いたいのであれば、FG起動を続けることに問題がない使い方である場合に限ります。

因みに、XcodeのBackground Modesで Act as a Bluetooth LE accessory を有効にしたとしても、BG起動中はiBeaconを発信することはできません。

また、 CoreBluetooth.framework にはiOS端末がiBeacon発信状態(アドバタイジング状態)かを判別するisAdvertising プロパティが存在します。
iBeaconを発信している状態でアプリをBG起動にした場合はisAdvertisingYES として返却されます。つまり、実際の状態と必ずしも一致するわけではないということです。

Centralの注意点

最後にCentralとしてiOS端末を使う場合の注意点についてお話しておきます。
iOS端末でiBeaconのレンジング処理を行う場合、FG起動時にしかレンジング処理を実行できません。
そのため、アプリの状態によらずレンジング処理が必要な仕様を実現することは不可能ということになります。

一応、didEnterRegionメソッドやdidExitRegionメソッドなどのデリゲートメソッドが実行された場合、約10秒間はアプリの状態によらずあらゆる処理が実行可能であるため、この間のみレンジング処理を実行してmajor, minorなどの値を取得することは可能です。

正しい動作検証を実施した上でiBeaconを扱うようにしましょう。

各種設定がOFFの場合のアラート表示について

さて、直接iBeaconとは関係がありませんが、iOS端末をPeripheralとして扱う場合は Bluetoothの設定 をONにしておく必要があります。iOS端末をCentralとして扱う場合は Bluetoothの設定位置情報サービスの設定 をONにしておく必要があります。

これらがOFFになっている場合、iBeaconの機能を利用することができないため、アプリ開発時にアラートを表示してユーザに知らせることを考えるかと思います。
さらに、できれば設定画面に飛ばしたいと思いますよね?

位置情報サービス の場合は、iOS8であれば設定画面へのURLスキームが復活したため、問題ありません。(iOS7.xでは設定画面への遷移は諦めましょう。)
Bluetooth の場合は CoreBluetooth.framework を利用していれば、難しくありません。(処理に CoreBluetooth.framework が不要なCentral側であっても設定画面に飛ばしたいのであれば、importする必要があります。)
※ 具体的にはCBPeripheralManagerもしくはCBCentralManagerの初期化時にoptionCBPeripheralManagerOptionShowPowerAlertKeyもしくはCBCentralManagerOptionShowPowerAlertKeyを設定すれば良いです。

以上がまとめとなります。
ぜひぜひ参考にして頂ければと思います。
本日はここまで。

Comments