次に、AccelerometerFilterクラスを利用するために、First View Controllerのインターフェースファイルに以下のように記述します。
FirstViewController.h
#import <UIKit/UIKit.h>
#import "AccelerometerFilter.h"
|
加速度センサーの利用準備
次に加速度センサーを活用するための準備を行います。First View Controllerに以下のようなメソッドを記述して下さい。
FirstViewController.m
//加速度計をスタート
-(void)initAccel {
//加速度計の分解能
double updateFrequency = 60.0;
//加速度計のフィルターを設定
filter = [[LowpassFilter alloc] initWithSampleRate:updateFrequency cutoffFrequency:5.0];
//加速度計のスタート
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:1.0 / updateFrequency];
[[UIAccelerometer sharedAccelerometer] setDelegate:self];
}
|
ここにある、「updateFrequency」は加速度センサーの分解能を示す値です。この設定では「60」となっていますが、これは1/60秒毎に加速度センサーから最新の加速度の値を取得するという意味です。
次に「filter」にローパスフィルターの設定値をセットし、加速度の取得をスタートさせるメソッドを読んでいます。なお、加速度センサーの利用に際しては、UIAccelerometerクラスがアプリとセンサーの仲立ち役となって、必要な処理や手続きを受け付けています。
最後に、First View Controllerのインターフェースファイル上に、以下の通りプロトコルの設定を施します。
FirstViewController.h
【変更前】
@interface FirstViewController : UIViewController
↓
【変更後】
@interface FirstViewController : UIViewController<UIAccelerometerDelegate>
|
加速度取得時によばれるメソッド
加速度取得の準備が終わったら、以下の通り、FirstViewController.mに新しい加速度の値を取得した際に呼ばれるメソッドを記述します。
FirstViewController.m
//加速度の値取得時に呼ばれるメソッド
//現在の設定では、1/60秒に一回、加速度が取得され、メソッドがよばれる
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
//値にフィルターを適用し
[filter addAcceleration:acceleration];
//歩行を検知するメソッドへ値を渡す
[self analyzeWalk:filter.x:filter.y:filter.z];
}
|
今回の設定だと1/60秒に一度、このメソッドが呼ばれ、加速度センサーの値が「acceleration」という引数で渡されます。この「acceleration」は、加速度の値を包括的に扱うUIAccelerationクラスのインスタンスであり、X軸・Y軸・Z軸の加速度(単位:「G」)を含んでいます。
このメソッドでは、取得した加速度の生データにローパスフィルターをかけ、ノイズを除去しています。その後、処理済みの加速度の値を「analyzeWalk」というメソッドに渡し、歩行を検知しています。この「analyzeWalk」は次に実装します。一部エラーが表示されるかもしれないですが、後ほど実装を進めると解消されるので、気にしないで下さい。
歩行を検知するメソッド
加速度の値をアプリ内に取り込む手段を整えたら、今度はその値をもとに、歩行を検知するメソッドを実装していきます。以下の通り、FirstViewController.mにコードを記述します。
FirstViewController.m
//歩行を検知するメソッド
-(void)analyzeWalk:(UIAccelerationValue)x :(UIAccelerationValue)y :(UIAccelerationValue)z {
//「山」の閾値
UIAccelerationValue hiThreshold = 1.1;
//「谷」の閾値
UIAccelerationValue lowThreshold = 0.9;
//合成加速度を算出
UIAccelerationValue composite;
composite = sqrt(pow(x,2)+pow(y,2)+pow(z,2));
//「山」の後に「谷」を検知すると1歩進んだと認識
if ( stepFlag == TRUE ) {
if ( composite < lowThreshold ) {
stepCount++;
stepFlag = FALSE;
}
} else {
if ( composite > hiThreshold ){
stepFlag = TRUE;
}
}
NSLog(@"%f %f %f %f", x, y ,z, composite);
//現在の歩数をラベルに表示
stepCountLabel.text = [NSString stringWithFormat:@"%d", stepCount];
}
|
このメソッドも、加速度の値が取得される度に呼ばれます。その際、新規に取得された加速度は、X・Y・Z軸別に引数として渡されます。
処理が始まると、まず、「山」と「谷」の基準となる閾値がセットされます。その後、引数を通して渡された加速度の値を元に、その瞬間の合成加速度を算出されます。
ここで、「山」の検出フラグである「stepFlag」が登場します。これは真偽を示す変数で、現在アプリが加速度の「山」を認識している場合は「真」となります。条件分岐により、合成加速度が「山」の閾値を上回り、かつ、「stepFlagが「偽」の場合、アプリは新たに「山」を検知したことを記憶します。合成加速度が「谷」の閾値を下回り、かつ、「stepFlag」が「真」である場合、初めて「1歩進んだ」ということが検知されます。このようにして、「山」と「谷」の連続を認識し、歩行の検知を行います。
「1歩」進んだということが認識されると、歩数を管理する「stepCount」が1歩分増やされ、新たな「山」を検知すべく「stepFlag」が「偽」となります。
アプリからのメール送信
iOS SDKは、アプリからメールを送信するための仕組みを提供しています。今回は、歩いた歩数をメール文として送信出来る仕組みを整えます。
まずは、以下の通り、Project Editorから「MessageUI.framework」をプロジェクトに取り込みます。
この後、First View ControllerからMessageUI.frameworkを利用するために、以下の通りFirstViewController.hに追記します。
FirstViewController.h
#import <UIKit/UIKit.h>
#import <MessageUI/MessageUI.h>
#import <MessageUI/MFMailComposeViewController.h> |
次に、First View Controllerのインターフェースファイルにプロトコルの設定を施します。以下の通り、FirstViewController.hを編集して下さい。
FirstViewController.h
【変更前】
@interface FirstViewController : UIViewController <UIAccelerometerDelegate>
↓
【変更後】
@interface FirstViewController : UIViewController <UIAccelerometerDelegate, MFMailComposeViewControllerDelegate>
|
プロトコルの設定が終わったら、「メール送信」ボタンと連動するIBAction型のメソッドとして、メール送信画面を起動する処理を記述します。以下の通り、FirstViewController.mにコードを記述します。
FirstViewController.m
//メール送信ボタンが押されたときの処理
-(IBAction)sendMail:(id)sender {
//件名と本文の内容
NSString *subject = @"歩きました!";
NSString *message = [NSString stringWithFormat:@"たった今、私は %d 歩きました!", stepCount];
//MFMailComposeViewControllerを生成
MFMailComposeViewController *mailPicker = [[MFMailComposeViewController alloc] init];
//MFMailComposeViewControllerからのDelegate通知を受け取り
mailPicker.mailComposeDelegate = self;
//件名を指定
[mailPicker setSubject:subject];
//本文を指定
[mailPicker setMessageBody:message isHTML:false];
//MailPicker(メール送信画面)を呼び出し
[self presentViewController:mailPicker animated:YES completion:nil];
}
|
ここの処理では、ボタンが押されると、件名と本文、それぞれの文字列を準備され、メール送信画面を提供するMFMailComposeViewControllerクラスのインスタンスである、「mailPicker」が生成されます。その後、「mailPicker」からのDelegate通知をFirst View Controllerで受けるように設定され、件名と本文を設定した上でメール画面を起動されます。
最後に、メール送信画面上で「送信」ボタン、または「キャンセル」ボタンが押された際に呼ばれるメソッドを実装します。
FirstViewController.m
//メール送信画面終了時に呼び出される
- (void) mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
//メール画面を閉じる
[controller dismissViewControllerAnimated:YES completion:nil];
}
|
これにて、アプリからメールを送信する仕組みは整いました。
歩数のリセット処理
歩数計に必須の機能として、歩数のリセットがあります。そこで、「リセット」ボタンが押された際に呼ばれるメソッドと、実際に歩数のリセットを行うメソッドの2つを実装します。
FirstViewController.m
//リセットボタンが押された時の処理
-(IBAction)resetButtonAction:(id)sender {
[self reset];
}
//リセット処理
-(void)reset {
//各変数をリセット
stepFlag = FALSE;
stepCount = 0;
//ラベルの値をリセット
stepCountLabel.text = [NSString stringWithFormat:@"%d", stepCount];
}
|
ここに書かれている処理はいたって単純かと思います。「reset」が呼ばれると単純に全てのフラグや変数、ラベルが初期状態に戻されます。
First View Controllerの初期処理
最後に、First View Controllerの初期処理を行います。
以下のように、FirstViewController.mの「viewDidLoad」を編集して下さい。
FirstViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
//歩数をリセット
[self reset];
//加速度計をスタート
[self initAccel];
}
|
これにて、First View Controllerの実装は完了となります。
Second View Controllerの実装
歩数計画面につづいて、今度は地図画面と連動するSecond View Controllerのコードを記述していきます。
メンバー変数の宣言とフレームワークの取り込み
まずはメンバー変数の宣言からから行なっていきます。
SecondViewController.m
@implementation SecondViewController {
//Location Manager
CLLocationManager *locationManager;
//緯度と経度のラベル
IBOutlet UILabel *longitudeLabel;
IBOutlet UILabel *latitudeLabel;
//位置情報の精度を示すラベル;
IBOutlet UILabel *accuracyLabel;
//Map View
IBOutlet MKMapView *map;
//Map Viewに表示するエリアを定義するMKCoordinateRegion
MKCoordinateRegion region;
}
|
ここにある、CLLocationManagerクラスは位置情報取得関係の処理を包括的に扱うクラスとなっています。さらに、MKMapViewクラスは地図を表示するためのMap Viewを提供し、MKCoordinateRegionクラスは地図の表示に関する各種設定を扱うものとなっています。
次に、Map ViewやLocation Managerを利用可能にするために、フレームワークの取り込みを行います。先程扱った「MessageUI.framework」と同様の手順で、「MapKit.framework」と「CoreLocation.framework」を取り込みます。
フレームワークを取り込んだら、Second View Controllerのインターフェースファイル(SecondViewController.h)に以下の記述を施します。
SecondViewController.h
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>
|
地図の初期化
まずは地図の初期化処理を行うメソッドから実装していきます。以下の通り、SecondViewController.mに記述します。
SecondViewController.m
-(void)initMapView {
//地図上に現在地マーカーを表示
map.showsUserLocation = YES;
//最初の中心点を東京タワーに設定
region.center.latitude = 35.658609;
region.center.longitude = 139.745447;
//ズームの設定
region.span.latitudeDelta = 0.005;
region.span.longitudeDelta = 0.005;
//地図の中心を移動
[map setRegion:region animated:YES];
}
|
最初に、以下に示すような「現在地マーカー」を表示させる設定をしています。
次に、「region」というメンバー変数に初期の中心地点の座標として「東京タワー」の緯度と経度をセットしています。その後、地図の縮尺(ズームの度合い)を示すパラメータとして上下方向・左右方向、ともに「0.005」をセットし、「setRegion」メソッドをもちいて東京タワーを地図上の初期中心点として反映させています。
位置情報の取得準備
地図の初期化処理を記述し終えたら、位置情報を取得するための準備を行うメソッドを実装します。以下の通り、SecondViewController.mに記述します。
SecondViewController.m
-(void)initLocation {
//Locaton Managerを作成
locationManager = [[CLLocationManager alloc] init];
//Location Managerの設定
locationManager.delegate = self;
[locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
//位置情報取得開始
[locationManager startUpdatingLocation];
}
|
加速度センサーの時と同様、「locationManager」のインスタンスを初期化し、Location Managerからのdelegate通知をSecond View Controllerで受けるようにします。この時、「setDesiredAccuracy」によって、「求める測位精度」を指定します。通常、このとき、「最高の測位精度」を高く設定すると、電池の減りが早くなります。一方、「大まかな測位」でも許容出来る場合、このオプションを調整することによって電池の減りを抑制することができます。以下にプリセットされたオプションの一覧を示します。
設定値 |
要求する精度 |
kCLLocationAccuracyBest |
出来る限り正確 |
kCLLocationAccuracyNearestTenMeters |
10 m |
kCLLocationAccuracyHundredMeters |
100 m |
kCLLocationAccuracyKilometer |
1km |
kCLLocationAccuracyThreeKilometers |
3km |
今回は、歩いている場所の現在地をリアルタイムで取得することが目的のため、「kCLLocationAccuracyBest」を選択します。その後、「startUpdatingLocation」メソッドによって位置情報の取得が開始されます。
最後に、インターフェースファイル上で位置情報取得に関連するプロトコルの設定を施します。以下の通りSecondViewController.hを編集します。
SecondViewController.h
【変更前】
@interface SecondViewController : UIViewController
↓
【変更後】
@interface SecondViewController : UIViewController <CLLocationManagerDelegate>
|
位置情報取得時の処理
ここまでで、位置情報を取得する準備は整いました。次は新しい位置情報を取得した時に呼ばれるメソッドを実装していきます。同時に失敗した時に呼ばれるメソッドも実装します。
まずは、新しい位置情報を取得した時に呼ばれるメソッドから記述していきます。以下の通り、SecondViewController.mに記述していきます。
SecondViewController.m
//位置情報が更新された時呼ばれるメソッド
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
//ラベルを更新
longitudeLabel.text = [NSString stringWithFormat:@"%f", newLocation.coordinate.longitude];
latitudeLabel.text = [NSString stringWithFormat:@"%f", newLocation.coordinate.latitude];
//誤差値
int accuracy = newLocation.horizontalAccuracy;
//誤差のラベルを更新
if (accuracy < 15) {
accuracyLabel.text = [NSString stringWithFormat:@"高 (%d m)", accuracy];
} else {
accuracyLabel.text = [NSString stringWithFormat:@"低 (%d m)", accuracy];
}
//現在地が移動したら、地図も追従
region.center = newLocation.coordinate;
[map setRegion:region animated:YES];
}
|
このメソッドは、新しい位置情報が検知される毎に呼び出されます。通常GPSの電波が正常に受信出来る場合、更新頻度は約1秒に1回となっています。GPSが受信できず、Wi-Fiや3G網の電波をもとに測位している場合、更新頻度はより少なくなります。
新しい位置情報はCLLocationクラスの引数として渡されます。ここで扱うCLLocationクラスは、位置情報における緯度や経度、高度、精度等を包括的に扱うためのクラスです。このメソッドでは、「newLocation」に最新の位置情報がすべて格納されています。
位置情報を新しく受信したら、まずは画面上のラベルの文字列が更新されます。その後「accuracy(精度)」が比較されます。この「accuracy」はセンサーが測位した座標と、デバイスがある本当の場所の間の最大誤差を「メートル」で表現しています。
例えば、「accuracy」の値が15mであった場合、現在地と測位データの間には最大で15mの誤差があり得ることを示しています。この値が大きければ大きいほど、即位した位置情報の精度は悪くなります。
今回、最大誤差が15m未満の場合は、位置情報取得精度を「高」とし、15m以上の場合は「低」としています。なお、位置情報取得精度が低い場合は、「現在地マーカー」は誤差の大きさに応じて、自動的に以下のように表示されます。
最後に、「region」に現在地をセットし、地図の中心が常に現在地となるようにしています。
位置情報失敗時の処理
ここまでで、新しい位置情報が取得された際に呼ばれるメソッドの実装は完了となります。次は、位置情報取得に失敗した時に呼ばれるメソッドを実装していきます。
以下のように、SecondViewController.mに記述します。
SecondViewController.m
//位置取得失敗時に呼ばれるメソッド
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
//エラーに応じてUIAlertを提示
if (error) {
NSString* message = nil;
if ([error code] == kCLErrorDenied) {
[locationManager stopUpdatingLocation];
message = [NSString stringWithFormat:@"このアプリは位置情報サービスが許可されていません。"];
} else {
message = [NSString stringWithFormat:@"位置情報の取得に失敗しました。"];
}
if (message != nil) {
UIAlertView* alert=[[UIAlertView alloc]
initWithTitle:@""
message:message delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
}
}
}
|
このメソッドは、位置情報取得失敗時に呼ばれ、エラーメッセージを表示するものです。エラーの多くは、ユーザーが位置情報取得を許可しないことによるものです。ユーザーが初回起動時に、位置情報の取得を許可しない場合、このエラーが呼ばれます。
もし誤って、「不許可」としてしまった場合は、ホーム画面の「設定」から「位置情報サービス」を選び、「万歩計」のセクションを「オン」にして下さい。
Second View Controllerの初期処理
最後に、Second View Controllerの初期処理を行います。
以下のように、SecondViewController.mの「viewDidLoad」を編集して下さい。
SecondViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
//位置情報取得準備
[self initLocation];
//地図を初期化
[self initMapView];
}
|
これにて、Second View Controllerの実装は完了となります。
UI画面とコードの関連付け
ここまでで、基本的にコードの記述は全て完了となります。そこで、これまでに設置したUI画面上の部品とコード上の処理を関連付けます。
歩数計画面の関連付け
まずは、歩数計画面のラベルから関連付けます。以下の通り、歩数を示すラベルをFirst View Controllerの「stepCountLabel」と関連付けます。
続いて、「リセット」ボタンと「メール送信」ボタンをそれぞれ、First View Controllerの「resetButtonAction」と「sendMail」と関連付けます。
地図画面の関連付け
地図画面では、以下の通り、「位置情報取得精度」、「緯度」、「経度」を示すラベルをそれぞれ、Second View Controllerの「acccuracyLabel」、「latitudeLabel」、「longitudeLabel」と関連付けます。
最後にMap ViewをSecond View Controllerの「map」と関連付けます。
ビルドと動作試験
これにて、すべての作業は完了となります。編集内容を全て保存し、ビルドを行なってください。このテキストの内容をすべて正しくやった場合、特に問題なくアプリが動作するはずです。
これまでは、シミュレーターを用いて動作検証を進めていた方も多いか思いますが、加速度センサーや位置情報センサーを用いる今回アプリは、実機での検証が必須となります。実機にインストールし、デバイスをポケットに入れ、歩数がカウントされることを確認して下さい。さらに、メール送信や地図の表示、位置の取得が行えることも確認して下さい。
ここで、もし歩数計の精度が芳しくない場合、歩数検知における「山」と「谷」の閾値を調整してみて下さい。
//「山」の閾値
UIAccelerationValue hiThreshold = 1.1;
//「谷」の閾値
UIAccelerationValue lowThreshold = 0.9;
|
まとめ
第9講では、加速度センサーの扱い方や位置情報の取得手法を学びました。また、加速度の向きの変化を利用した歩数検知手法も取り扱いました。さらに、地図の利用やアプリからのメール送信の仕組みもカバーしました。
今回のアプリはスマートフォンの特徴とも言える機能を網羅的に扱っています。スマートフォンには様々なセンサーが取り付けられており、それらを組み合わせることによって、今までは考えられなかったようなことが可能になります。加速度を用いた歩行検知や、現在地を常に追う地図も、これらの典型例といえます。
次回は更に視点を変え、Twitterと連携するアプリを作っていきます。