端的にいうと
- Apple謹製のテストアプリ配布機能TestFlightが優秀
- TestFlightで本番/テスト環境をそれぞれ配布したい
- 本番/テスト環境を把握できるテクニックを紹介
- SwiftUIで実装しているがUIKitでも適用可能
-
1) TestFlight概要
iOS/iPad用アプリの配布向けにテストアプリを配布しフィードバックを得てアプリの質を高めることはアプリストアへアプリを公開する際の心理的障壁を下げてくれる。
2021年においてはテストアプリ配布サービスを検討する際、Apple謹製かサードパーティ製を選べる選択肢がある。
Apple謹製のサービス名称はTestFlightと呼ばれiOSアプリ開発ポータルに組み込まれた管理画面と、テスト担当がアクセスできるiOSアプリの組み合わせで提供されている。
TestFlightはApple謹製ではあるがmacOSまではサポートしてはくれないようだ(2021年4月現在)。
サードパーティ製テストアプリサービスだと開発者ポータルとテスト担当向けにWebサイトが用意されていることが多いがTestFlightは開発者はWeb、テスト担当はTestFlightアプリをインストールして使用する。
TestFlightアプリはサードパーティーと比べると以下5つのメリットがある。
- テストアプリ更新毎に通知が届く
- テスト開始までの余計な手順(プロファイルのインストール)が省略可能
- ホーム画面のアプリ名に目立つ色(オレンジマーク)が付く
- 外部テスターに制限がない
- 企業アカウントに属する(招待された)AppleIDユーザーは即テスト可能
デメリットとしては、
- 本番アプリ同様テストアプリも審査対象
- アプリのID(AppID)は1アプリに1つだけ許可する
があるが2021年4月時点では1日以内にテストアプリの配布が可能となっている(注:Appleの繁忙期には各種審査が遅くなる傾向がある)。
配布先を管理するためのグループ機能も提供されているがグループ機能自体はサードパーティ製でも提供されている。
Note: サードパーティ製テストアプリ配布サービスとしては、
がある。
2) TestFlightアプリ配布テクニック(小手先)
TestFlightのデメリットとしてアプリのID(AppID)は1アプリに1つだけ許可されている点が大きい。何を言っているかというと見た目も機能も同じだが別アプリIDを割り振ったアプリはTestFlightでは許可されない。
例えばアプリが接続するWebサービスがProduction環境とStaging環境があるとして、テストユーザーが混乱しないようにProduction/Staging環境ごとにアプリを配布するテクニックが使用できない。
TestFlightでProduction/Staging環境ごとにアプリを配布するためのテクニックとしては、
- TestFlightのグループ管理機能を使ってProduction/Staging環境ごとにテストユーザーをセパレートする
- ビルドバージョンの奇数/偶数でProduction/Staging環境を判断する
など運用ルールを決めてしまう手もある。
3) TestFlightアプリ配布テクニック(代替アイコン)
2で紹介したのは運用上のテクニックであるため、Production/Staging環境が1つのアプリとして配布されている場合に起きる問題、SNS投稿アプリであればStaging環境で試す投稿をProduction環境で投稿するなどといったトラブルが起きないとも限らない。
テストユーザーに対してテスト対象アプリのアプリバージョンやアプリビルドバージョンについて注意を促すにしてもテストユーザーの資質によるものが大きいので期待しすぎることは難しい。
この投稿で紹介したいのはiOSの代替アイコン(Alternate Icon)機能でアプリアイコンとメッセージで動作環境を明示的に示す方法である。
iOSの代替アイコン(Alternate Icon)機能自体は辞書アプリなど購読型アプリなどでアプリの見た目を購入対象に切り替えるなどの意図で提供された機能でiOS10.3から提供されている。アプリ開発者に身近なものとしてはGitHubアプリのアイコン切り替え機能がある。
今回は代替アイコン機能を使って動作環境を明示してみる。以下にSwiftUIの実装コードを示す。
import SwiftUI | |
import UIKit | |
// Debug.xcconfig | |
// OTHER_SWIFT_FLAGS = $(inherited) "-D" "DEBUG" | |
// DebugStage.xcconfig | |
// OTHER_SWIFT_FLAGS = $(inherited) "-D" "DEBUG" "-D" "STAGE" | |
// Release.xcconfig | |
// OTHER_SWIFT_FLAGS = $(inherited) | |
// ReleaseStage.xcconfig | |
// OTHER_SWIFT_FLAGS = $(inherited) "-D" "STAGE" | |
@main | |
struct TestModeApp: App { | |
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String | |
let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as! String | |
var body: some Scene { | |
WindowGroup { | |
ContentView(version: version, build: build) | |
.onAppear(perform: { | |
#if STAGE | |
let taregetIconName: String? = "stage" | |
#else | |
let taregetIconName: String? = nil | |
#endif | |
if UIApplication.shared.alternateIconName != taregetIconName { | |
UIApplication.shared.setAlternateIconName(taregetIconName) { error in | |
print("/!\\ setAlternateIconName error! \(String(describing: error))") | |
} | |
} | |
}) | |
} | |
} | |
} | |
struct ContentView: View { | |
let version: String | |
let build: String | |
var body: some View { | |
Text("version \(version)(\(build))") | |
.padding() | |
} | |
} |
上記コードでは各種リソースが不足しているのでレポジトリに公開しているのでチェックアウトして試して欲しい。
notoroid/TestModeAlternateIcon: Test mode sample using the Alternate Icon feature of the iOS SDK.
サンプルコードではProduction/Staging環境に対応するためSchemaを増やしている。Staging環境だけ環境変数STAGEを定義し、#if 〜 #end分で切り分けている。
#if STAGE
#else
#end
STAGE提示自体はXcodeの外部設定ファイルに記述する。
// Debug.xcconfig
OTHER_SWIFT_FLAGS = $(inherited) "-D" "DEBUG"
// DebugStage.xcconfig
OTHER_SWIFT_FLAGS = $(inherited) "-D" "DEBUG" "-D" "STAGE"
// Release.xcconfig
OTHER_SWIFT_FLAGS = $(inherited)
// ReleaseStage.xcconfig
OTHER_SWIFT_FLAGS = $(inherited) "-D" "STAGE"
iOSの代替アイコン(Alternate Icon)機能を使用する際の注意は代替アイコン切り替えが発生するタイミングでOSはアイコンが切り替えられたメッセージを表示する仕様となっている。
上記仕様のためアプリ画面の初期化が完了していない状態で処理を呼び出すとエラーが返ってくる。
代替アイコンの設定は、UIApplication.shared.setAlternateIconName()メソッドを呼び出す。
UIApplication.shared.setAlternateIconName("stage") { error in
print("/!\\ setAlternateIconName error! \(String(describing: error))")
}
代替アイコンはinfo.plistのCFBundleAlternateIcons、CFBundleAlternateIcons~ipad で定義した画像リソースのみ利用できる。
ここに定義のスクリーンショット。
代替アイコンを戻す場合はUIApplication.shared.setAlternateIconName()メソッドのパラメーターにnilを渡せば良い。
UIApplication.shared.setAlternateIconName(nil) { error in
print("/!\\ setAlternateIconName error! \(String(describing: error))")
}
UIApplication.shared.setAlternateIconName()メソッドは呼び出し毎にメッセージを表示してしまうので同じ値が代入されるのを防ぐ必要がある。切り替えられたアイコン設定はアプリを削除しない限り永続化される。
3-1) SwiftUIでの実装
SwiftUIではViewのonAppearモデファイヤーで呼び出せば良い。サンプルコードでは必ず呼び出されることを意図してトップレベルのViewで定義している。
UIKitでは開始画面のviewDidAppearイベントで記述するとSwiftUI同等の機能が実現できる、
4) まとめ
TestFlight使用時に配布トラブルを減らすテクニックを紹介した。iOSの代替アイコン(Alternate Icon)機能は知っていたが実際に仕様する機会がなかったがTestFlightでのテストアプリ配布時に使えるのではないかと思い立ってサンプルコードを作成した。
iOSの代替アイコン(Alternate Icon)機能自体は10.3から変更がない仕様となっている。想定されるのは整理対象ないし仕様変更される可能性もあるので2021年以降に利用の際に主要な機能として提供することは避けた方がいいかもしれない。