iOS7で位置情報サービスを使う場合のUX設計上の配慮

iOSで地図やGPSを利用する場合には、CoreLocationフレームワークを利用します。
利用すること自体はとても簡単なのですが、利用を開始するに至るまでのシーンで、UX上、考慮すべき点がいくつかありました。

1. 利用可能なiOS端末を絞り込む

CoreLocationフレームワークで利用出来る位置情報サービスには、その精度に応じて2段階あります。

 1. 一般的な精度
 2. GPSを使った高い精度

GPSを使った高い精度の位置情報サービスを利用するには、GPSが搭載されているiPhoneiPadである必要があります。(iPod touchにはGPS搭載モデルはありません)

上記の精度は、アプリケーションのInfo.plistファイルにUIRequiredDeviceCapabilitiesキーを追加することで指定出来ます。

 1. 文字列location-servicesを指定します
 2. 文字列gpsを指定します

  

App Storeは、このキーの情報を使用して、リストに含まれている機能のないデバイスへのアプリケーションのダウンロードを防ぎます。

2. 位置情報サービスの機能がオンになっているかどうか確認する

位置情報サービスは、以下の3通りの方法でオフにすることが出来ます。

 方法1 位置情報サービス全体をオフにする
 方法2 個別のアプリケーションに対してのみ、位置情報サービスをオフにする
 方法3 機能制限により位置情報サービスをオフにする

方法1や方法2はおなじみかと思いますが、方法3は今回改めてリファレンスを読んで確認しました。
iOSバイスを子供などに持たせる場合に使う「機能制限」ですが、その中にも個別に位置情報サービスがありました。

  

位置情報サービスがオンになっているかどうかを確認する手段は、CoreLocationフレームワークでは2種類提供されています。

 (A) (BOOL) [CllocationManager locationServicesEnabled]
 (B) (CLAuthorizationStatus)[CllocationManager authorizationStatus]

どちらもクラスメソッドなので、インスタンスを生成する必要も無く利用出来ます。
(A)では、方法1、方法2、方法3の区別が出来ず、とにかく位置情報サービスが利用出来るか否か、ということが判定出来ます。
どのような方法で位置情報サービスがオフになっているかを判別したい場合は、(B)のメソッドを利用することになります。

アプリケーションで位置情報サービスを利用する際には、(A)または(B)の方法で確認してから使うようにすると、間違いが無くていいですね。

さらには、位置情報サービスがオフになっている場合に、オンにすることをユーザーに促すことが出来れば、ユーザーの戸惑いも減りそうです。

3. 位置情報サービをオンにするように促す方法について(教えてください)

以上を踏まえて、ここからが本エントリーの本題となります。

標準アプリの「マップ」では、位置情報サービスがオフになっている時に「現在地を表示」ボタンをタップすると、下記のダイアログが表示されます。

  

このダイアログは「設定」ボタンをタップすることで設定アプリの位置情報サービス画面を直接開くことが出来るため、設定を変更したいユーザーにとってはとても便利です。

しかしながらこの、設定アプリの特定の画面を開くことは、iOS 5.0まではその方法が公開されていたのですが、iOS 5.1以降は非公開となってしまい、アプリケーションから直接開くことは出来なくなってしまいました。

色々と試行してみたのですが、現状では(iOS 7.1 beta2)標準アプリと全く同じダイアログをプログラム的に作成することは不可能なようでした。

ただし、位置情報サービスがオフになっている時にCLLocationManagerのstartUpdatingLocationを呼び出すと、標準アプリと同じダイアログが表示される「場合もある」ことは分かりました。

今のところ、どのような場合どのような条件で表示されるのか分かっていないので、これに頼ることは止めて、(A)や(B)の方法で確認した上で、独自のダイアログを表示させることとしました。ほんとは「設定」ボタン付きのダイアログをつくりたいんですけど。。

参考に、と思ってGoogleGoogle Mapアプリを確認してみたところ、やはりGoogleも独自ダイアログ(「設定」ボタン無し)だったので、今のところこのアプローチしか無いようです。

  

標準アプリの「マップ」が表示している「設定」ボタン付きのダイアログをプログラム的に生成する方法について、ご存知の方がいらっしゃいましたらご教示をお願いします。

TestFlight SDKをAdHocビルド時のみ有効となるようにプロジェクトに組み込む

TestFlight SDKをプロジェクトに組み込む際、公式のREADMEに記載された方法だと、全てのスキーム、全てのターゲットに対してTestFlight SDKが組み込まれます。

しかし実際に必要なのはAdHocビルド時のみで、Releaseビルドには含めたくありませんしDebugビルドにも不要です。

ビルドの際に、プロジェクト設定を変更していると、切替ミスなどが起きてしまいそうなので、そういったことが起きないような形でプロジェクトに組み込んでみました。

1. TestFlight SDKのファイルをプロジェクトに組み込む

ダウンロードしたTestFlight SDKにはいくつかのファイルが含まれていますが、実際に必要なのはこの2ファイルです。
2つのファイルをドラッグ&ドロップでプロジェクトに追加し、グループ化します。(グループ化は必須ではありません)

2. AdHoc用のConfigurationを作成する

プロジェクト設定画面を開いて、Configurationsの+ボタンを押して、Duplicate "Release" Configuration します。
複製したConfigurationをAdHocと改名します。

3. プロビジョニングプロファイルを設定する

新たに増えたAdHocと、既存のDebug、Releaseのそれぞれに適切なプロビジョニングプロファイルを設定します。
AdHoc時にはAdHoc用のプロビジョニングプロファイルを、Release時にはAppStore用のプロビジョニングプロファイルを設定します。

4. AdHoc Configuration時のみ、libTestFlight.aをリンクする

プロジェクト設定画面を開いて、Build SettingsタブのOther Linker Flagsの、AdHocに-lTestFlightと-lzを追加します。
1つ目でlibTestFlight.aがリンクされ、2つ目でlibz.dylibがリンクされることになります。

5. 開発、AdHoc用のスキームと、AppStore用のスキームを分ける

Manager Schemes... から、歯車アイコンを押しDuplicateを選択します。新たに出来たスキームは、「プロジェクト名+AppSotre」とし、AppStore用のスキームと分かるようにします。

開発、AdHoc用のスキームは、以下のようにArchive時のBuild ConfigurationをAdHocに設定します。

一方で、AppStore用のスキームは、以下のようにArchive時のBuild ConfigurationにReleaseと設定します。


6. 用途に応じてスキームを切り替える

2つのスキームを作成しました。
プロジェクト名だけの方は、開発、AdHoc用ですので、Debug実行も出来ますし、ArchiveすればTestFlight SDKが組み込まれたAdHocビルドが生成されます。
プロジェクト名+AppStoreの方は、ArchiveするとAppStore用のビルドが生成されます。


以上です。
毎回、プロジェクト設定を変えて、AdHoc用ビルドとAppStore用ビルドを切り替えたりしていましたが、スキームを分けることでスキームの切替に従ってそれぞれのビルドが出来るようになりました。

(参考)
TestFlight SDK公式ドキュメント https://testflightapp.com/sdk/doc/1.0/
TestFlightのSDKをAppStore申請用ではバイナリに含めなくする方法 - basuke の日記

アプリのバージョン、ビルドを「設定」の初期値として設定する

「設定」アプリの自分のアプリの項目に、アプリのバージョンやビルドを表示することがあります。

地味にどうやるんだろうと思って調べました。

結論から言うと、Run Scriptを追加するだけで出来ました。plistファイルの読み書きを行うコマンドPlistBuddyを用います。以下、その仕組みと共に説明します。

まず、「設定」アプリの初期値はSettings.BundleのRoot.plistに記載されます。

一方で、アプリケーションのバージョン、ビルドは、プロジェクト設定ファイルに記載されます。

やりたいことは、「アプリケーション名」-Info.plistに記載されているバージョン、ビルドをRoot.plistファイルのバージョンのDefault ValueとビルドのDefault Valueに記載することとなります。

そこで、PlistBuddyコマンドを用いて、以下のようなRun Scriptを追加しました。


#プロジェクト設定ファイルのバージョン番号とビルド番号を
#Settings.bundleの設定ファイルのそれぞれの初期値に設定するためのスクリプト
versionNumber=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" 'NSUserDefaults Sample/NSUserDefaults Sample-Info.plist')
/usr/libexec/PlistBuddy -c "Set :PreferenceSpecifiers:1:DefaultValue $versionNumber" 'NSUserDefaults Sample/Settings.bundle/Root.plist'

buildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" 'NSUserDefaults Sample/NSUserDefaults Sample-Info.plist')
/usr/libexec/PlistBuddy -c "Set :PreferenceSpecifiers:2:DefaultValue $buildNumber" 'NSUserDefaults Sample/Settings.bundle/Root.plist'

プロジェクト名の「NSUserDefaults Sample」は適当に読み替えて下さい。

このRun Scriptを追加することで、ビルドの度にRoot.plistのバージョン、ビルドのDefault Valueに実際のバージョン、ビルドが設定されるので、Root.plistの更新忘れが無くなっていい感じです。

ViewControllerのiPhone/iPadユニバーサル化

iPhoneiPadでほとんど同じ画面を実装する際に、
今までは.h、.m、.xibファイルをそれぞれ個別に作成していました。

機能や操作はほとんど同じなのに、ただレイアウトが
iPhoneiPadで異なる、という状況でこのようにしてました。

しかし実は、別々にするのはxibファイルだけで良かったことが分かりました。

  • 共通
    • MyViewController.h
    • MyViewController.m
  • iPhone
  • iPad
    • MyViewController_iPad.xib

この場合、ViewControllerのインスタンスは、以下のように生成することになります。

MyViewController *viewController;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
    viewController = [[[MyViewController alloc] initWithNibName:@"MyViewController_iPhone" bundle:nil] autorelease];
} else {
    viewController = [[[MyViewController alloc] initWithNibName:@"MyViewController_iPad" bundle:nil] autorelease];
}

こうすることで、レイアウトのみ別々のxibファイルで指定出来て、
機能や操作の実装は共通化出来るので良いですね。