読者です 読者をやめる 読者になる 読者になる

ROADTO みちログ

ひとのみちのブログ。大阪でiOSアプリの道を歩くフリーランス。

GeoFenceとCoreMotionを組み合わせたバックグラウンド常駐監視の一案

仕事へのパッションが抑えられません。

ジオフェンシングとモーションセンサーを使って
バックグラウンドやロック状態でも何かをしたいときの一案。

ジオフェンシングって知ってますか?

そうだね、プロテインだね。

ではEnjoy ジオフェンシングとモーションセンサー!

やりたいこと

ある領域内にいるうちは定期的に何かしたい。

何かのトリガーでバックグラウンドでアプリを起動させても動くのって10秒ほど。
がんばって延長させても最長10分弱。

そうじゃなくて定期的に何かしたい。

やることの要約

  • 指定領域内に入ったらモーションセンサー開始
  • モーションセンサーで歩くたびに何かする
  • 指定領域内は継続
  • バックグラウンドでもロック時も動作開始、継続
  • 指定領域から出たらモーションセンサー停止

前提

  • Xcode6.3.1
  • 検証機はiPhone6plus iOS8.3。
  • AppStoreに申請したことはない。
  • SwiftじゃないよObjective-Cです。

「・・・」のところは本筋から外れるので間引いています。
気になる方はサンプルコードからどうぞ。
サンプルコード

実装のまえに

プロテイ、、プロジェクトの設定をします。

位置情報「常に使用」を有効にします。
* Info.plistにNSLocationAlwaysUsageDescriptionを追加 f:id:hitonomichi:20150529192716p:plain バックグラウンドで動かす設定をします。
* TARGETS - Capabilities - Background Modes をON
* Location updatesにチェックをつける
f:id:hitonomichi:20150529192733p:plain

やっと実装

まずはインポート。

余談ですがframeworkに追加しなくてよくなったのね。
framework追加なしでエラーになりません。

#import <CoreLocation/CoreLocation.h>
#import <CoreMotion/CoreMotion.h>

CLLocationManager初期化と監視領域の指定。

CLLocationCoordinate2DMakeで指定した緯度軽度を中心に、
CLLocationDistanceで指定した領域を監視します。

//CLLocationManager初期化
self.locationManager = [[CLLocationManager alloc] init];
・・・
self.locationManager.delegate = self;

//Region初期化
CLLocationCoordinate2D coordinate 
    = CLLocationCoordinate2DMake(37.324540, -122.041241);//アメリカ(クパチーノ)
CLLocationDistance radiusOnMeter = 100.0;//範囲指定
self.geoRegion = [[CLCircularRegion alloc] initWithCenter:coordinate
                                                   radius:radiusOnMeter
                                               identifier:@"jp.roadto.ROADTO-Geo"];

//位置情報の許可状況に合わせて動作開始
if ([self.locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
    // iOS8
    switch ([CLLocationManager authorizationStatus]) {
        case kCLAuthorizationStatusNotDetermined:
            NSLog(@"位置情報の許可が未設定なのでユーザに確認する");
            [self.locationManager requestAlwaysAuthorization];
            break;
        case kCLAuthorizationStatusAuthorizedAlways:
            NSLog(@"「常に許可」されている");
            break;
            ・・・
    }
} else {
    // iOS7以下
    [self.locationManager startUpdatingLocation];
}

続きましてCLLocationManagerDelegateメソッドたち

requestStateForRegionで監視が始まったときとか
requestAlwaysAuthorizationの位置情報使用確認後とかに通知されます。

領域内に入ったときにモーションセンサーをONにし、
領域外になったときにモーションセンサーをOFFにします。

説明はコメントにて。

#pragma mark - CLLocationManagerDelegate
// CLLocationManager初期化時または
// アプリの位置情報の許可状態ステータスが変更されたときに通知される
-(void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
    switch (status) {
        case kCLAuthorizationStatusNotDetermined:
            break;
        case kCLAuthorizationStatusAuthorizedAlways:
            NSLog(@"「常に許可」");
            // 領域監視スタート
            [self.locationManager startMonitoringForRegion:self.geoRegion];
            break;
            ・・・
    }
}

//領域監視がスタートしたときに通知
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
    //現在の領域状態を確認。
    //「- locationManager:didDetermineState:forRegion:」が呼ばれます。
    [self.locationManager requestStateForRegion:region];
}

//領域内に入ったときに通知
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
    NSLog(@"領域内になったよ");
    
    //モーションセンサー 開始 内容は次の章で説明。
    [self startMotionActivity];
}

//領域の状態について通知。状態が変わるたびに呼び出される。
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region {
    switch (state) {
        case CLRegionStateInside:
            NSLog(@"領域内");
            //モーションセンサー 開始 内容は次の章で説明。
            [self startMotionActivity];
            break;
            ・・・
    }
}

//領域から出たことを通知
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
    NSLog(@"領域外になったよ");
    
    //モーションセンサー 停止
    [self stopMotionActivity];
}

あとはモーションセンサー

CMMotionActivityManagerを使ってモーションを取ります。
今回は歩いているか否かを使っていますが、他にもいろんな状態をとれます。
「//do something」でしたい何かの処理を書きます。

#pragma mark - モーションセンサー
//モーションセンサー 初期化
- (CMMotionActivityManager *)getActivityManager {
    if (!self.motionActivityManager) {
        self.motionActivityManager = [[CMMotionActivityManager alloc] init];
    }
    return self.motionActivityManager;
}

//モーションセンサー 開始
- (void)startMotionActivity {
    NSLog(@"モーションセンサー 開始");
    
    //iOS7,8ともに必要
    [self.locationManager startUpdatingLocation];    //←キモ。iOS7,8ともに必要です。
    
    if([CMMotionActivityManager isActivityAvailable]){
        NSOperationQueue *queue = [NSOperationQueue mainQueue];
        CMMotionActivityManager *motionActivityManager = [self getActivityManager];
        [motionActivityManager startActivityUpdatesToQueue:queue withHandler:^(CMMotionActivity *motionActivity){
            
            //do something

            NSString *log = [NSString stringWithFormat:@"motion %@", motionActivity.walking ? @"そうだね。プロテインだね。" : @"んーんーんー。"];
            NSLog(log);
            ・・・
        }];
    }
}

//モーションセンサー 停止
- (void)stopMotionActivity {
    NSLog(@"モーションセンサー 停止");
    CMMotionActivityManager *motionActivityManagerUpdate = [self getActivityManager];
    [motionActivityManagerUpdate stopActivityUpdates];
}

テストに出ます

「←キモ」のところ。
startMotionActivity内で「-locationManager startUpdatingLocation」を呼んでいます。
これ自体は位置情報の更新監視をスタートさせるものですが
なぜかこれを書くとモーションセンサーもバックグラウンドで動いてくれます。

バックグラウンドでもロック状態でも起動するし、
フォアグラウンドで起動して、バックグラウンドやロック状態になっても動き続けます。

尚、「M7」モーションセンサーはiPhone5sから搭載なので、
iPhone5以前は使えません。

結果

サンプルコードを起動させます。
サンプルコードでは画面に出力させています。

歩いているかどうかを判定しています。
歩いてると出てますね。「そうだね。プロテインだね。」
f:id:hitonomichi:20150529193201p:plain

バックグラウンドでも動いていますね。「そうだね。プロテインだね。」
f:id:hitonomichi:20150529193317p:plain

以上です。

エヴィバディパッション!

自己紹介
メガネは売っていません
高浜 一道(たかはま かずみち)
大阪でiOSアプリの道を歩くフリーランス。
iPad・iPhoneとサーバを連携した、JSONやDBといったキーワードが出てくるツール系が多めです。
お仕事のご依頼やご連絡はこちらから。
お仕事ください!
Web : ROADTO
ML : k-takahama(a)roadto.jp

個人的なアカウントはこちら。
Tw : @hitonomichi
Fb : kazumichi.takahama