第09講 万歩計
印刷を御希望の方はページ一番下の「印刷用ページ」より印刷してください。
PDFファイルとして保存したい場合も「印刷用ページ」より保存できます。
見出しに「★」マークがついた箇所は説明動画がございます。

第9講のポイント

第9講のポイント

  • 加速度計の扱い方と、センサーデータの解析
  • iOS上での位置情報の取得と扱い方と地図の表示
  • アプリからのメール送信方法

はじめに

前回の「ウェブブラウザー」では、iOSアプリ内にてウェブページを表示する手法を学びました。また、リレーショナルデータベースの基本や、SQLiteの扱い方を中心に学んだかと思います。

第9講の課題アプリは「万歩計」となります。「万歩計」はその名の通り、歩数をカウントするアプリです。iOSデバイスには様々なセンサーが内蔵されていますが、今回は、その中でも加速度計の扱い方を勉強します。同時に位置情報の扱い方や、アプリ内での地図の表示手法なども扱います。

このアプリは初のTabbed Application(タブによって構成させるアプリ)となります。1つ目のタブに歩数計画面を、2つ目のタブに現在地を示す地図画面を表示します。


それでは、早速開発を始めて行きましょう! 

プロジェクトの立ち上げと設定

新しいアプリを作る際は、まず、新規プロジェクトを立ち上げとアプリの設定を行います。

新規プロジェクトの立ち上げ

これまでと同じ要領で、新規プロジェクトを立ち上げます。今回は初めて、「Tabbed Application」を使います。



Product Name Pedometer
Company Identifier com.rainbowapps
Device Family iPhone
Use Storyboard チェックを入れる
Use Automatic Reference Counting チェックを入れる
Include Unit Tests チェックを入れる

アプリの設定

新規プロジェクトを立ち上げたら、アプリの設定を行います。ここで、アプリのアイコンやアプリの表示タイトル(Bundle Display Name)、サポートするデバイスの向き(Device Orientation)を以下のように、設定します。


外部素材のインポート

まず、今回使用する外部素材ファイルを一気にインポートしてしまいましょう。


今回使用する素材は以下の通りです。

 icon.png  アイコン画像
 AccelerometerFilter.h  加速度計フィルター用ファイル
 AccelerometerFilter.m  加速度計フィルター用ファイル


アイコン設定とサポートするデバイスの向きの設定

素材のインポートが完了したら、Project Editorを開き、アイコンをRetina Displayと書かれたエリアの上にドラッグします。
サポートするデバイスの向きとアプリの名前は以下の通り設定します。


Bundle display nameの設定

アプリの表示タイトルを決めている設定項目「Bundle Display Name」を変更します。
Project Editorの「info」タブをクリックして「Bundle Display Name」の項目を次のように変更していきます。

 Bundle Display Name  万歩計





画面のデザイン

アプリの設定が完了したら、画面のデザインを行なっていきます。今回は初めて「Tab Bar」があるアプリケーション(Tabbed Application)を作成します。

Tabbed Applicationとは?

Tab Barとは、画面下部のタブを選ぶエリアを指します。iOSアプリを使ったことのある人は、必ずどこかで見たことあるでしょう。例えば、購入当初から入っている音楽再生アプリの「ミュージック」もTab Barを備える代表的なアプリの1つとなっています。


今回使用する「Tabbed Application」は、2つのタブの土台と、それらと連携する2つのView Controllerを予め備えています。
それでは、Tabbed ApplicationのStoryboardを見て行きましょう。



初期状態で、このようにTab Bar Controllerと2つのタブ用の画面が用意されています。また、それらはSegueによって予めつながっています。ここにある「First View」が左側のタブの画面、「Second View」が右側のタブの画面となっています。既にSegueが設定されているところからも分かる通り、この状態で、タブ間の移動に必要な処理はすべて組み込まれています。

今回は、左側のタブ(First View)に歩数を表示する画面を、右側のタブ(Second View)に地図を表示する画面をそれぞれつくっていきます。

Autolayoutの無効化

画面レイアウトを3.5インチ(iPhone4S以前)と4インチ(iPhone5以降)に対応させるための設定を行います。
まずはXcode4.5からデフォルトで有効になっているAutolayoutを無効にします。
※Autolayoutはまだ不安定ですので本講座はAutolayoutを無効にして画面デザインを行っていきます。
下記のように①View Controller全体を選択し、②「File Inspector」に変更して、③「Use Autolayout」のチェックを外してください。

歩数計画面のデザイン

まずは、「First View」と書かれた方から編集していきます。初期状態のLabel等をすべて削除し、背景を黒色にします。
同時に、タブのタイトルをダブルクリックし、「歩数」に変更します。


次に静的なラベル(内容が変更されないラベル)を設置します。


静的なラベルの配置が終わったら、実際に歩数を表示するラベルを設置します。


最後に、以下の通り、「リセット」ボタンと「メール送信」ボタンを押します。を設置します。

異なる画面サイズの対応

3.5インチ及び4インチ画面にて画面のレイアウトが崩れないよう対応していきます。
まず画面下部の2つのボタン要素を選択した状態で「Size Inspector」を選択しAutosizingの箇所を下の部分だけ赤色実線になるように変更します。これでラベル要素が上とセンター揃えで固定されました。



地図画面のデザイン

歩数計画面のデザインが終わったら、地図画面をデザインします。歩数計画面同様、初期状態のラベル等を全て削除し、タブのタイトルを「現在地」とします。


次に静的なラベル(内容が変更されないラベル)を画面上部に設置します。


静的なラベルの配置が終わったら、実際の位置情報の精度・緯度・経度を示すラベルを設置します。これらに関しては後ほど解説していきます。


最後に、画面全体に、地図を表示するためのMap Viewを設置します。ライブラリーエリアよりMap Viewを選び、以下のように画面上に配置して下さい。


異なる画面サイズの対応

3.5インチ及び4インチ画面にて画面のレイアウトが崩れないよう対応していきます。
まず画面のMapView要素を選択した状態で「Size Inspector」を選択しAutosizingの箇所を下記のとおりに変更します。
これでMapView要素が画面のサイズに合わせ拡大・縮小するようになります。



これで画面のデザインは完了です。

加速度と歩行検知

ここでは、そもそも加速度とは何なのかを解説しながら、加速度をもとに歩行を検知する手法を考えていきます。

加速度とは?

中学校や高等学校の理科の授業で「加速度」を学んだ方も多いかと思いますが、加速度とは、その名の通り「加速の度合い」を示す数値で、単位時間当たりの速度の変化率を表しています。

iPhoneやiPadを含む、昨今のスマートフォンやタブレットは加速度を測るための「加速度センサー」を内蔵されており、デバイスが静止状態から動いたときに、その動きを検知することができます。これにより、iPhoneを上下に振る動作や、デバイスが別のものにぶつかった時の衝撃を検知することができます。さらに、地球には常に重力が働いているため、その重力を検知することで、デバイスの向きの検知などができます。iPhoneやiPadを横向きにすると、その向きに応じて画面も回転するのは、加速度センサーによって重力を検知しているからです。

iOSデバイスに内蔵されているのは「3軸加速度センサー」となります。これは、左右方向(X軸)、上下方向(Y軸)、前後方向(Z軸)の加速をそれぞれ独立した状態で検知できるセンサーとなっています。なお、下記の図の通り、加速度は力の働く向き(加速の向き)によって、「正(+)」の値で表現されたり、「負(−)」の値で表現されたりします。



ここで、少し加速度の基本についておさらいしましょう。加速度は「加速」の度合いを示す値のため、静止時は「±0」となります。車が急発信するように、静止中のデバイスが突然大きく動いた場合、加速度は大きな値を示し、ゆっくりと動かした場合は小さな値を示します。

一方で、前述した通り地球には重力が働いており、9.80()の加速度によって我々は常に地上に押し付けられています。この加速度は「重力加速度」と呼ばれ、9.80()=1 Gとも表現されます。つまり、地球上でデバイスが静止している場合は、この重力加速度を検知することになります。

ここまでの説明を聞いても、イマイチはっきりしないと思うので、実際の事例をもとに考えていきましょう。iPhoneを平らな机の上に、画面を上にした状態で水平に置いた場合、重力によって加速度センサーのZ軸方向に「−1.0 G」の重力が検知されます。このとき、X軸方向とY軸方向は何も動きがないので、加速度センサーの値はそれぞれ「±0 G」となります。このようにiOSの加速度センサーは「G」という単位を用いて検出値を返します。なお、これらはあくまで理論値であり、実際のセンサーの検出値は多少の誤差を含んだ値となります。



加速度センサーの軸検出値(G)
 X軸  ±0(理論値)
 Y軸  ±0(理論値)
 Z軸  −1.0(理論値)

歩行の検知

それでは、加速度を用いて歩行を検知する手法について少し考えていきましょう。ここの内容は直接プログラミングとは関係ない、簡単な数学と物理の知識となりますが、参考までの内容として読み進めて頂ければと思います。

まず、「歩行」という人間の動作状態の定義から考えていきましょう。人間が歩くとき、足は常に前後に動いています。実際は、より複雑な動き方をするでしょうが、今回は考え方を簡単にするために、あえてこのように定義してみます。



今回は、歩行者のズボンのポケットに入ったiPhoneを利用して、歩数をカウントすることにします。歩行者が1歩進むと、足が前後に1回ずつ動くため、iPhoneの加速度センサーは「+」の向きの加速と「−」の向きの加速をそれぞれ1回ずつ検知することになります、この加速の向きの変化を用いて歩行の検知を行なっていきます。

ここで、留意すべき点が1つあります。前述したように、iPhoneは3つの軸上に働く加速を、それぞれ独立した形で計測しています。従って、これら3つの軸のうち、どれか1つのみに着目してしまうと、ポケットの中のiPhoneの向きによっては、正確に加速を検知できない場合があります。

この問題をクリアするためには、複数の手法があるのですが、今回は「合成加速度」というものを利用します。合成加速度は軸に関係なく、デバイスに働く加速度を1つのベクトルとして捉えた加速度の値で、以下の計算式によって求められます。

但し、X・Y・Zはそれぞれの軸に働く加速度の値を示す

この合成加速度を利用することによって、ポケットの中デバイスの向きを考慮することなく、デバイスに働く加速の向きの変化を捉えることができます。以下の図は、歩行時にデバイスが検知する合成加速度の例を、時系列に沿ってグラフにしたものです。



このグラフでは、「1.0」を中心に合成加速度の値が増減していることがわかります。この増減こそが足の前後運動、即ち歩行動作なのです。ここで、「1.0」は重力加速度を示しており、2つの青い線は「閾値(しきいち)」を示しています。閾値とは、歩行動作を検知する上での基準値を示します。閾値は歩行検知の精度のカギを握る値であり、デバイスやユーザーに応じて、念入りに調整する必要があります

上の図の矢印で示したように、「谷」(下の閾値を下回る箇所)と「山」(上の閾値と上回る箇所)を交互に検知した場合、「1歩進んだ」ということになります。よって、上の図の通り加速度を検知した場合、合計で「10歩」の歩行を検知することになります。
もう1点、先程の図では、歩行を示す大きな「山」や「谷」以外に、緑色の矢印で示した箇所のように、小刻みな加速度の増減が見受けられます。これらは「ノイズ」と呼ばれ、今回の歩行動作とは一切関係のないものです。このようなノイズが多く見受けられる場合、歩行動作の誤検知を誘発しかねません。よって、これらノイズは「フィルター」と呼ばれるものによって、除去する必要があります。

今回はフィルターの中でも「ローパスフィルター」と呼ばれるものを用いて、ノイズの除去を行います。冒頭で、外部データをインポートする際に取り込んだ、「AccelerometerFilter.h」と「AccelerometerFilter.m」は、ローパスフィルターの機能を提供するクラスを記述したファイルであり、小刻みに発生するノイズを除去するために使われるものです。原理の詳しい解説は割愛しますが、興味のある方はぜひ調べてみて下さい。

このセクションで解説した加速度センサーを用いた歩行の検出は、「コンテクスト抽出」と呼ばれるテクニックの典型的な例の1つです。コンテクスト抽出は、様々なセンサーを使って、ユーザーやその周辺環境の状況や状態を推察するテクニックの総称で、任天堂のWiiコントローラーなどに応用されています。

位置情報

実際のコーディングを行う前に、位置情報の概念についても少し解説していきます。ここで解説する内容は、位置情報を理解する上で最低限必要な知識となっています。既に知っている方も多いかと思いますが、確認の意味も込めて読み進めて頂ければと思います。

地球上の「位置」の表し方

まず、「場所」を他人に伝えるとき、皆さんは何を用いるか考えてみて下さい。代表的な例を以下に示します。
  • 駅名(「渋谷駅」など)
  • 店舗やビル、名所の名前(「東京タワー」など)
  • 住所(「東京都港区六本木1-1」など)
このような「場所の表現」は人間にとってはわかりやすいものの、機械にとっては非常に理解しづらいと言えるでしょう。また、外国の方にこのような「場所」を伝えても、なかなかわかってもらえないかもしれません。

そこで、コンピューター上で位置情報を取り扱う際、最もよく利用される手法として、「緯度」と「経度」を用いた地点の表現が挙げられます。



緯度は赤道を基準として南北へそれぞれ0度から90度までの値で表現されます。その際、赤道よりも北側は「北緯」、南側は「南緯」として表現されます。経度はイギリスの旧グリニッジ天文台を通る子午線を基準に、東西へそれぞれ0度から180度までの値で表現されます。緯度同様、イギリスを基準に東側は「東経」、西側は「西経」として表現されます。

緯度と経度を用いることで、地球規模で全ての地点を座標として扱えます。例えば、東京タワーは「北緯35.658609・東経139.745447度」として表現できます。この表現手法は全世界標準となっています。iOS上で緯度・経度を扱う際、単位はすべて「度」となります。また、東経と北緯は「正(+)」の値として、西経と南緯は「負(−)」の値として扱われます。

A-GPSによる位置情報の取得

ご存知の方も多いかと思いますが、iPhoneや3G通信機能付きのiPadは位置情報を取得する際にA-GPSと呼ばれる機能を用います。A-GPS(Assisted-GPS)は、GPS衛星とWi-Fi・3G網の基地局の電波をそれぞれ組み合わせ、正確な位置情報をデバイスに提供するための仕組みとなっています。

GPSとは米国が打ち上げた約30個の衛星から構成させる全地球測位システムであり、これら衛星からの電波を捉えることによって、地球上どこにいても、自分の位置の緯度と経度を測位することができるシステムです。

一方でGPSはビルの影や内部、地下では利用できません。そこで、iOSはGPS衛星による信号の他に、Wi-Fiや3G網の電波を用いて、可能な限り正確な位置情報を推定し、提供する仕組みを持っています。Wi-Fiや3G網の電波のみ受信可能な場合でも、iOSデバイスは現在地のおおよその目処は定めることができます。しかし、この場合の測位結果は大きな誤差を伴ったものとなります。

iPod TouchやiPad(Wi-Fiモデル)の場合、GPSの信号は受信できないため、これらデバイスで位置情報を測位する際は、Wi-Fiや3G網の電波のみを利用することになります。GPSを使う場合と使わない場合のプログラミングの手法は全く同じですが、iPod TouchやiPad(Wi-Fiモデル)は、iPhoneなどと比べ、精度の高い位置情報測位はできないことを留意しなければなりません。

iOSデバイスはGPSの電波受信の可否や、周辺の状況で測定精度は大幅に変動します。アプリによっては、「精度の高い位置情報」のみを利用したいという場合もあるでしょう。幸いにも、iOSはアプリの中で取得した位置の精度を把握する仕組みをもっています。その利用方法は後ほど解説します。

コードの記述

ここでは、実際に万歩計のコーディングを行なっていきます。「万歩計」の実装そのものは前回の「ウェブブラウザー」などと比較すると少し簡単なものとなっています。

First View Controllerの実装

まずは、歩数計画面と連動するFirst View Controllerのコードから記述していきます。

メンバー変数の宣言

まずはメンバー変数の宣言からから行なっていきます。

FirstViewController.m
@implementation FirstViewController {

    //加速度計のフィルター

    AccelerometerFilter *filter;

    //「山」の検出フラグ

    BOOL stepFlag;

    //歩数の合計

    int stepCount;

    //歩数を表示するラベル

    IBOutlet UILabel *stepCountLabel;

}


ここにある「filter」というメンバー変数はAccelerometerFilterクラスのインスタンスです。AccelerometerFilterクラスは、前述したローパスフィルターを提供するライブラリーのものです。「山」の検出を管理するフラグの「stepFlag」に関しては後ほど説明します。

次に、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 allocinitWithSampleRate:updateFrequency cutoffFrequency:5.0];

    

    //加速度計のスタート

    [[UIAccelerometer sharedAccelerometersetUpdateInterval:1.0 / updateFrequency];

    [[UIAccelerometer sharedAccelerometersetDelegate: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 allocinit];

    //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 allocinit];

    //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と連携するアプリを作っていきます。
Comments