万歩計アプリ

講義の目標

  • 加速度情報の取得ができるようになること
  • GPS情報(位置情報)の取得ができるようになること
  • GoogleMapを使った位置表示の仕組みを理解すること
  • Cocoapodsが使えるようになること

はじめに

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

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

完成品のサンプルは以下よりダウンロードできます。イメージが湧かない方は是非ご確認ください。

iOS完成品サンプル(万歩計アプリ)

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

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

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

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

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

入力項目 入力値
Product Name Pedometer
Team None
Oganization Name 任意(”ALJ”など自分が所属している組織の名前を入れる)
Oganization Identifier jp.co.al-j
Language Swift
Devices iPhone
Use Core Data チェックを外す
include Unit Tests チェックを外す
include UI Tests チェックを外す

アプリの設定

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

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

素材ダウンロード

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

ファイル名 説明
iPhone-Notification-20@2x.png Notification用x2画像
iPhone-Notification-20@3x.png Notification用x3画像
iphone-icon-60@2x.png アイコンx2画像
iphone-icon-60@3x.png アイコンx3画像
iphone-setting-29@2x.png 設定x2画像
iphone-setting-29@3x.png 設定x3画像
iphone-spotlight-40@2x.png Spotlight用x2画像
iphone-spotlight-40@3x.png Spotlight用x3画像
splash-iphone-2x.png スプラッシュ画像x2画像
splash-iphone-ios7-retina4.png スプラッシュiOS7画像
splash-iphone-ios8-47.png スプラッシュiOS8画像 4.7インチ画像
splash-iphone-ios8-55-portrait.png スプラッシュiOS8 5.5インチ画像

アイコン設定

画像素材のiconフォルダの中のアイコン画像をプロジェクトに取り込んでください。

スプラッシュ画像設定

画像素材のsplashフォルダの中のスプラッシュ画像をプロジェクトに取り込んでください。

サポートするデバイスの向きの設定

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

Bundle Nameの設定

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

入力項目 入力値
Bundle Name 万歩計

Frameworkのインポート

iOS8のCoreMotionフレームワークにCMPedometer という歩数関連のデータを取得することができる API が追加されました。このクラスを使うと歩数/走行距離/昇り降りしたフロアの数などを計測できたり、過去に遡ってデータを参照できたりします。

今回はこのCore Motion フレームワークを使用しますのでその準備を行います。

「Linked Frameworks and Libraries」の「+」ボタンを押してください。

次に「Searchボックス」に「CoreMotion」と入力し、検索にヒットした「CoreMotion.framework」を選択し、「add」ボタンをタップしてください。

同様に今回アプリからメールを送信するため、「MessageUI.framework」というものも追加します。
「MessageUI.framework」はアプリからメールを送信するための仕組みを提供しています。今回は、歩いた歩数をメール文として送信出来る仕組みを整えます。

上記同様「Linked Frameworks and Libraries」の「+」ボタンを押し、「Searchボックス」に「MessageUI」と入力し、検索にヒットした「MessageUI.framework」を選択し、「add」ボタンをタップしてください。

するとプロジェクト内に「CoreMotion.framework」、「MessageUI.framework」が追加されたと思います。

CocoaPodsの導入

今回作成するアプリで「GoogleMap」を利用しますので「GoogleMap」を利用するための機能を実装しなければなりません。

しかし、これらの作業を自分で実装するのはとても大変です。そこでこれらの作業を簡単に行えるライブラリというものを今回導入していきます。

ただ、ライブラリの導入も少々面倒くさいところもあります。そのライブラリの導入をお手軽にできるCocoaPodsというツールを使っていきます。

CocoaPodsとは

CocoaPodsとは、iOSの様々なライブラリを管理してインストールするツールで、rubyでいうbundlerのようなもので、テキストファイルに必要なライブラリを書いてコマンドを打つだけで、インストールされて使えるようになるとても便利なツールです。

CocoaPodsのインストール

まずはCocoaPodsをMacにインストールしていきます。

先ほど作成した「Pedometer」のフォルダーをターミナルにドラッグしてください。

下記のコマンドをターミナルにて入力し、エンターキーを押します。

sudo gem update --system

するとパスワードを尋ねられますので、自身のMacのパスワードを入力してください。
パスワードを入力するとづらづらと処理が走りますので少し待ってください。
下記のようになっているとOKです。

Updating rubygems-update
Fetching: rubygems-update-2.4.8.gem (100%)
Successfully installed rubygems-update-2.4.8

【中略】

If you do not wish to install this documentation in the future, use the
--no-document flag, or set it as the default in your ~/.gemrc file. See
'gem help env' for details.
RubyGems system software updated

次に実際にCocoaPodsをインストールしていきます。下記のコマンドをターミナルへ入力し、エンターキーを押してください。

sudo gem install -n /usr/local/bin cocoapods

すると下記のようなメッセージが出てくると思います。
このようになっていればインストールは完了です。
(Successfully installed cocoapods-x.x.xが常時されていればインストール完了です。)

Successfully installed cocoapods-1.0.1
Parsing documentation for cocoapods-1.0.1
1 gem installed

最後にCocoaPodsを使えるようにセットアップを行います。下記のコマンドをターミナルで実行してください。

pod setup

ここで下記のメッセージが表示されていれば導入は完了です。

Setting up CocoaPods master repo

【中略】

Setup completed

これで今後はこの手順を踏まずにCocoaPodsが利用できます。次に実際にCocoaPodsを使っていこうと思います。

Cocoapodsの利用

以下のコマンドを実行してください。

pod init

エディター(Xcode)で先ほど作成した「Podfile」を開いてください。

「Podfile」を開きましたら、以下を追加してください。

Podfile

# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'

target 'Pedometer' do
# Comment this line if you're not using Swift and don't want to use dynamic frameworks
use_frameworks!

# Pods for Pedometer
# ▼▼ 追加 ▼▼
pod 'GoogleMaps'
# ▲▲ 追加 ▲▲
end

ここで保存をしてエディターを閉じてください。
次に先ほどのターミナル画面の戻って下記のコマンドを入力してください。

pod install

これで今回使うライブラリの「GoogleMap」を導入できました。
Cocoapodsでの作業は以上になります。
ここで前回同様に一度プロジェクトを閉じてください。ここで再度「Pedometer」のフォルダを開くと「Pedometer.xcworkspace」というファイルが作成されていると思います。

Cocoapodsを使用した場合はこちらのファイルを開いて作業を行いますので、「Pedometer.xcworkspace」を開いてください。

それでは、引き続き今まで同様にストーリーボードで画面を作成していきます。

画面のデザイン

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

Tabbed Applicationとは?

Tab Barとは、画面下部のタブを選ぶエリアを指します。iOSアプリを使ったことのある人は、必ずどこかで見たことあるでしょう。例えば、購入当初から入っているApp Storeのアプリも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)に地図を表示する画面をそれぞれつくっていきます。

歩数計画面のデザイン

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

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

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

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

歩数計画面のAuto Layout設定

Auto Layoutの設定をします。下図のとおりに設定してください。

プレビュー画面で下図のように表示されればOKです。

地図画面のデザイン

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

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

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

ここの画面には「GoogleMap」を配置します。直接「 GoogleMap」」はストーリーボードでは配置できないので、まずUIViewを配置します。
(後ほどGogle Map用のViewとして修正します。)

地図画面のAuto Layout対応

プレビュー画面で下図のように表示されればOKです。

UIオブジェクトをコードと結びつける

Main.storyboardとコードをアシスタントエディタで接続します。

FirstViewController

FirstViewController.swift

import UIKit

class FirstViewController: UIViewController {

@IBOutlet weak var stepCountLabel: UILabel!

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

@IBAction func sendMail(sender: AnyObject) {
}
@IBAction func resetButtonAction(sender: AnyObject) {
}

}

SecondViewController

SecondViewController.swift

import UIKit

class SecondViewController: UIViewController {

@IBOutlet weak var accuracyLabel: UILabel!
@IBOutlet weak var latitudeLabel: UILabel!
@IBOutlet weak var longitudeLabel: UILabel!

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}


}

これにて、画面デザインは完了となります。

歩行の検知

今回の万歩計アプリは歩行の検知を自動で行ってくれる「CMPedometer」という「CoreMotion」の提供してくれている機能を実装します。詳しい原理は今回は割愛しますが、興味のある方はぜひ調べてみて下さい。

位置情報

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

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

まず、「場所」を他人に伝えるとき、皆さんは何を用いるか考えてみて下さい。代表的な例を以下に示します。

  • 駅名(「渋谷駅」など)
  • 店舗やビル、名所の名前(「東京タワー」など)
  • 住所(「東京都港区六本木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.swift

class FirstViewController: UIViewController {
@IBOutlet weak var stepCountLabel: UILabel!
// ▼▼ 追加 ▼▼
// 歩数関連のデータを取得するための変数
var pedometer = CMPedometer()
//歩数の合計
var stepCount = Int()
// ▲▲ 追加 ▲▲

ここにある「pedometer」というメンバ変数が「CoreMotion.framework」の歩数関連のデータを取得することができる API です。このクラスを使うと歩数、走行距離、昇り降りしたフロアの数などを計測できたり、過去に遡ってデータを参照できたりします。

次に「CMPedometer」クラスを使用できるように「CoreMotion」をインポートします。

FirstViewController.swift

import UIKit
// ▼▼ 追加 ▼▼
import CoreMotion
// ▲▲ 追加 ▲▲

歩数取得

次に歩数を取得するための関数を作成していきます。First View Controllerに以下のようなメソッドを記述して下さい。

FirstViewController.swift

// ▼▼ 追加 ▼▼
// 歩数を取得する関数
func startStepCounting() {
// CMPedometerの確認
if(CMPedometer.isStepCountingAvailable()){
// 計測開始
self.pedometer.startUpdates(from: NSDate() as Date) {
(data: CMPedometerData?, error) -> Void in
DispatchQueue.main.async(execute: { () -> Void in
if(error == nil){
// 歩数
self.stepCount = Int(data!.numberOfSteps)
// 結果をラベルに出力
self.stepCountLabel.text = "\(self.stepCount)"
}
})
}
}

}
// ▲▲ 追加 ▲▲

まずは「CMPedometer」を利用できるか確認する必要があります。なので「if」で条件分岐をしていきます。
次に実際に計測を開始するために「pedometerのstartPedometerUpdatesFromDate」メソッドを実行します。
ここでは「dispatch」という非同期処理を行っています。
最後に「dataのnumberOfSteps」プロパティに歩数情報が入っているのでその値をstepsに代入して実際に歩数を表示するラベルに出力しています。

これで歩数の取得は完了です。

アプリからのメール送信

まずはFirst View ControllerからMessageUI.frameworkを利用するために、以下の通りFirstViewController.swiftに追記します。

import UIKit
import CoreMotion
// ▼▼ 追加 ▼▼
import MessageUI
// ▲▲ 追加 ▲▲

次に、First View Controllerに「MailCompose」のデリゲートを宣言します。

【変更前】

class FirstViewController: UIViewController {

【変更後】

class FirstViewController: UIViewController, MFMailComposeViewControllerDelegate {

デリゲートの宣言が終わったら、「メール送信」ボタンと連動するIBAction型のメソッドとして、メール送信画面を起動する処理を記述します。以下の通り、FirstViewController.swiftにコードを記述します。

FirstViewController.swift

//メール送信ボタンが押されたときの処理
@IBAction func sendMail(_ sender: AnyObject) {
// ▼▼ 追加 ▼▼
//MFMailComposeViewControllerを生成
let mailViewController = MFMailComposeViewController()
//件名と本文の内容
let subject = "歩きました!"
let message = String(format: "たった今、私は %d 歩きました!", stepCount)
//MFMailComposeViewControllerからのDelegate通知を受け取り
mailViewController.mailComposeDelegate = self
//件名を指定
mailViewController.setSubject(subject)
//本文を指定
mailViewController.setMessageBody(message, isHTML: false)
//MailPicker(メール送信画面)を呼び出し
self.present(mailViewController, animated: true, completion: nil)
// ▲▲ 追加 ▲▲
}

ここの処理では、ボタンが押されると、件名と本文、それぞれの文字列を準備され、メール送信画面を提供するMFMailComposeViewControllerクラスのインスタンスである、「mailViewController」が生成されます。その後、「mailViewController」からのDelegate通知をFirst View Controllerで受けるように設定され、件名と本文を設定した上でメール画面を起動されます。

最後に、メール送信画面上で「送信」ボタン、または「キャンセル」ボタンが押された際に呼ばれるメソッドを実装します。

FirstViewController.swift

// ▼▼ 追加 ▼▼
//メール送信画面終了時に呼び出される
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
// ▲▲ 追加 ▲▲

これにて、アプリからメールを送信する仕組みは整いました。

歩数のリセット処理

歩数計に必須の機能として、歩数のリセットがあります。そこで、「リセット」ボタンが押された際に呼ばれるメソッドと、実際に歩数のリセットを行うメソッドの2つを実装します。

FirstViewController.swift

//リセットボタンが押された時の処理
@IBAction func resetButtonAction(sender: AnyObject) {
// ▼▼ 追加 ▼▼
self.reset()
// ▲▲ 追加 ▲▲
}

// ▼▼ 追加 ▼▼
//リセット処理
func reset() {
//各変数をリセット
self.stepCount = 0
pedometer = CMPedometer()

// 歩数取得開始
startStepCounting()

//ラベルの値をリセット
self.stepCountLabel.text = "\(self.stepCount)"
}
// ▲▲ 追加 ▲▲

ここに書かれている処理はいたって単純かと思います。「reset」が呼ばれると単純に全てのフラグや変数、ラベルが初期状態に戻されます。

First View Controllerの初期処理

最後に、First View Controllerの初期処理を行います。
以下のように、FirstViewController.swiftの「viewDidLoad」を編集して下さい。

FirstViewController.swift

override func viewDidLoad() {
super.viewDidLoad()
// ▼▼ 追加 ▼▼
// 歩数取得開始
startStepCounting()
// ▲▲ 追加 ▲▲
}

これにて、First View Controllerの実装は完了となります。

SecondViewControllerの実装

歩数計画面につづいて、今度は地図画面と連動するSecond View Controllerのコードを記述していきます。

メンバー変数の宣言とフレームワークの取り込み

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

SecondViewController.swift

class SecondViewController: UIViewController {
@IBOutlet weak var accuracyLabel: UILabel!
@IBOutlet weak var latitudeLabel: UILabel!
@IBOutlet weak var longitudeLabel: UILabel!
@IBOutlet weak var mapBaseView: UIView!
// ▼▼ 追加 ▼▼
//Location Manager
let locationManager = CLLocationManager()
// ▲▲ 追加 ▲▲

ここにある、CLLocationManagerクラスは位置情報取得関係の処理を包括的に扱うクラスとなっています。さらに、GMSMapViewクラスはGoogleMapを表示するためのクラスを提供します。

次に、Map ViewやLocation Managerを利用可能にするために、フレームワークの取り込みを行います。先程扱った「MessageUI.framework」と同様の手順で、「CoreLocation.framework」を取り込みます。

フレームワークを取り込んだら、Second View Controller(SecondViewController.swift)に以下の記述を施します。

SecondViewController.swift

import UIKit
// ▼▼ 追加 ▼▼
import CoreLocation
import GoogleMaps
// ▲▲ 追加 ▲▲

次にGoogleMapを使うための準備をします。

Google Maps SDK for iOSからAPIキーを取得する。

次にGoogleMapのAPIキーというものを設定します。GoogleMapは「Google APIs Console」の「Services」タブで「Google Maps SDK for iOS」を「ON」にして、今回のアプリ用にAPIキーを発行しないと使用できません。
なのでその設定を行います。

Google Maps SDK for iOSの利用手順

まずGoogle Developer Consoleにアクセスして新しいプロジェクトを作成します。

Google Developer Console

下図のとおりの操作でApi Keyを取得してください。

最後にAppDelegate.swiftにAPIキーの設定を行います。下記のように「AppDelegate.swift」にコードを追加してください。

AppDelegate.swift

import UIKit
// ▼▼ 追加 ▼▼
import GoogleMaps
// ▲▲ 追加 ▲▲
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// ▼▼ 追加 ▼▼
GMSServices.provideAPIKey("APIキーをペースト")
// ▲▲ 追加 ▲▲
return true
}

これでGoogleMapを利用するための準備が整いました。

位置情報の取得準備

まず「Info.plist」というファイルに位置情報を取得してもいいかの許可を取るための設定を行わないといけません。
なので「Supporting Files」の中にある「Info.plist」を右クリックして開いてください。

次に以下の要素を追加します。

  • LSApplicationQueriesSchemes
  • NSMotionUsageDescription
  • NSLocationWhenInUseUsageDescription
  • NSLocationAlwaysUsageDescription
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- ▼▼ 追加 ▼▼-->
<key>LSApplicationQueriesSchemes</key>
<array>
<string>googlechromes</string>
<string>comgooglemaps</string>
</array>
<key>NSMotionUsageDescription</key>
<string>Use MotionUsage</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Use CoreLocation</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Use CoreLocation</string>
<!-- ▲▲ 追加 ▲▲-->
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>

これで設定は完了です。

次に位置情報を取得するための準備を行うメソッドを実装します。以下の通り、SecondViewController.swiftに記述します。

SecondViewController.swift

// ▼▼ 追加 ▼▼
func initLocation() {
//Location Managerの設定
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
//位置情報取得開始
locationManager.startUpdatingLocation()
}
// ▲▲ 追加 ▲▲

加速度センサーの時と同様、「locationManager」のインスタンスを初期化し、Location Managerからのdelegate通知をSecond View Controllerで受けるようにします。この時、「desiredAccuracy」によって、「求める測位精度」を指定します。通常、このとき、「最高の測位精度」を高く設定すると、電池の減りが早くなります。一方、「大まかな測位」でも許容出来る場合、このオプションを調整することによって電池の減りを抑制することができます。以下にプリセットされたオプションの一覧を示します。

設定値 要求する精度
kCLLocationAccuracyBest 出来る限り正確
kCLLocationAccuracyNearestTenMeters 10 m
kCLLocationAccuracyHundredMeters 100 m
kCLLocationAccuracyKilometer 1km
kCLLocationAccuracyThreeKilometers 3km

今回は、歩いている場所の現在地をリアルタイムで取得することが目的のため、「kCLLocationAccuracyBest」を選択します。その後、「startUpdatingLocation」メソッドによって位置情報の取得が開始されます。

最後に、インターフェースファイル上で位置情報取得に関連するプロトコルの設定を施します。以下の通りSecondViewController.swiftを編集します。

SecondViewController.swift

【変更前】

class SecondViewController: UIViewController {

【変更後】

class SecondViewController: UIViewController, CLLocationManagerDelegate, GMSMapViewDelegate {

位置情報取得時の処理

ここまでで、位置情報を取得する準備は整いました。次は新しい位置情報を取得した時に呼ばれるメソッドを実装していきます。同時に失敗した時に呼ばれるメソッドも実装します。
まずは、新しい位置情報を取得した時に呼ばれるメソッドから記述していきます。以下の通り、SecondViewController.swiftに記述していきます。

SecondViewController.swift

// ▼▼ 追加 ▼▼
//位置情報が更新された時呼ばれるメソッド
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let newLocation = locations.last else {
return
}
//ラベルを更新
longitudeLabel.text = String(format: "%f", newLocation.coordinate.longitude)
latitudeLabel.text = String(format: "%f", newLocation.coordinate.latitude)
//誤差値
let accuracy = newLocation.horizontalAccuracy
//誤差のラベルを更新
if (accuracy < 15) {
accuracyLabel.text = String(format: "高 (%.0f m)", accuracy)
} else {
accuracyLabel.text = String(format: "低 (%.0f m)", accuracy)
}
}
// ▲▲ 追加 ▲▲

このメソッドは、新しい位置情報が検知される毎に呼び出されます。通常GPSの電波が正常に受信出来る場合、更新頻度は約1秒に1回となっています。GPSが受信できず、Wi-Fiや3G網の電波をもとに測位している場合、更新頻度はより少なくなります。

新しい位置情報はCLLocationクラスの引数として渡されます。ここで扱うCLLocationクラスは、位置情報における緯度や経度、高度、精度等を包括的に扱うためのクラスです。このメソッドでは、「newLocation」に最新の位置情報がすべて格納されています。

位置情報を新しく受信したら、まずは画面上のラベルの文字列が更新されます。その後「accuracy(精度)」が比較されます。この「accuracy」はセンサーが測位した座標と、デバイスがある本当の場所の間の最大誤差を「メートル」で表現しています。

例えば、「accuracy」の値が15mであった場合、現在地と測位データの間には最大で15mの誤差があり得ることを示しています。この値が大きければ大きいほど、即位した位置情報の精度は悪くなります。

今回、最大誤差が15m未満の場合は、位置情報取得精度を「高」とし、15m以上の場合は「低」としています。

//誤差のラベルを更新
if (accuracy < 15) {
accuracyLabel.text = String(format: "高 (%.0f m)", accuracy)
} else {
accuracyLabel.text = String(format: "低 (%.0f m)", accuracy)
}

ここまでで、新しい位置情報が取得された際に呼ばれるメソッドの実装は完了となります。次は、位置情報取得に失敗した時に呼ばれるメソッドを実装していきます。

以下のように、SecondViewController.swiftに記述します。

SecondViewController.swift

// ▼▼ 追加 ▼▼
//位置取得失敗時に呼ばれるメソッド
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
let message = "位置情報の取得に失敗しました。"

let alert = UIAlertController(title: "", message: message, preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "キャンセル", style: .cancel) { (action) -> Void in
print("後で")
}
alert.addAction(cancelAction)
present(alert, animated: true, completion: nil)
}
// ▲▲ 追加 ▲▲

このメソッドは、位置情報取得失敗時に呼ばれ、エラーメッセージを表示するものです。エラーの多くは、ユーザーが位置情報取得を許可しないことによるものです。ユーザーが初回起動時に、位置情報の取得を許可しない場合、このエラーが呼ばれます。

もし誤って、「不許可」としてしまった場合は、ホーム画面の「設定」から「位置情報サービス」を選び、「万歩計」のセクションを「オン」にして下さい。

地図の初期化

まずはMain.storyboardに配置したUIViewをGoogle Map用のViewにします。
Main.storyboardを開いて、UIViewを選択し、下図のようにGMSMapViewに修正します。

次にアシスタントモードにしてGMSMapViewをドラッグしてIBOutletとして関連付けします。

SecondViewController.swift

class SecondViewController: UIViewController, CLLocationManagerDelegate, GMSMapViewDelegate {
@IBOutlet weak var accuracyLabel: UILabel!
@IBOutlet weak var latitudeLabel: UILabel!
@IBOutlet weak var longitudeLabel: UILabel!
@IBOutlet weak var mapView: GMSMapView! // GoogleMap用のView

地図の初期化処理を行うメソッドから実装していきます。以下の通り、SecondViewController.swiftに記述します。

SecondViewController.swift

// ▼▼ 追加 ▼▼
func initMapView() {
mapView.isMyLocationEnabled = true
mapView.delegate = self
}
// ▲▲ 追加 ▲▲

ここではまずGoogleMapをSecondViewControllerに配置するための初期化を行っています。

次にGoogleMapに現在位置のマーカを表示させる実装を行います。先ほど現在位置を取得するために作成した「locationManager」メソッドに下記を追加してください。

SecondViewController.swift

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
【中略】
// ▼▼ 追加 ▼▼
// GoogleMapに現在位置を表示するために現在位置情報を渡す
let pos : GMSCameraPosition = GMSCameraPosition.camera(
withLatitude: newLocation.coordinate.latitude, // 緯度
longitude: newLocation.coordinate.longitude, // 経度
zoom: 16.0 // ズーム(0 - 19)数が大きいほどクローズアップ
)
mapView.camera = pos
// ▲▲ 追加 ▲▲
}

これでGoogleMapに現在位置を表示することができました。

Second View Controllerの初期処理

最後に、Second View Controllerの初期処理を行います。
以下のように、SecondViewController.swiftの「viewDidLoad」を編集して下さい。

SecondViewController.swift

override func viewDidLoad() {
super.viewDidLoad()
// ▼▼ 追加 ▼▼
//地図を初期化
self.initMapView()
//位置情報取得準備
self.initLocation()
// ▲▲ 追加 ▲▲
}

これにて、Second View Controllerの実装は完了となります。

ビルドと動作試験

これにて、すべての作業は完了となります。編集内容を全て保存し、ビルドを行なってください。このテキストの内容をすべて正しくやった場合、特に問題なくアプリが動作するはずです。

これまでは、シミュレーターを用いて動作検証を進めていた方も多いか思いますが、加速度センサーや位置情報センサーを用いる今回アプリは、実機での検証が必須となります。実機にインストールし、デバイスをポケットに入れ、歩数がカウントされることを確認して下さい。さらに、メール送信や地図の表示、位置の取得が行えることも確認して下さい。

まとめ

今回は、加速度センサーの扱い方や位置情報の取得手法を学びました。また、加速度の向きの変化を利用した歩数検知手法も取り扱いました。さらに、GoogleMapの利用やアプリからのメール送信の仕組みもカバーしました。

今回のアプリはスマートフォンの特徴とも言える機能を網羅的に扱っています。スマートフォンには様々なセンサーが取り付けられており、それらを組み合わせることによって、今までは考えられなかったようなことが可能になります。歩行検知や、現在地を常に追う地図も、これらの典型例といえます。