単一の Xcode プロジェクトで複数ターゲットの iOS アプリをビルドする
iOS アプリを開発していると共通の実装でコンテンツだけ差し替えた別アプリをリリースしたいことがある。例えばゲームならキャラクターやパラメータ、観光案内アプリであれば地図や写真を土地ごとに変更する、など。
このような対応をおこなう場合、外部スクリプトでコンテンツ部分を差し替えるとかリポジトリのブランチを利用してプロジェクト構成を切り替えるのかと想像していた。しかし調査してみたら Xcode の標準機能だけで実現できたので方法をまとめておく。
Xcode プロジェクトのターゲット管理
Xcode で iOS アプリのプロジェクトを作成すると初期状態ではターゲットがひとつだけ指定されている。これを複数にする手順は以下。
- Xcode でプロジェクトを開く
- 画面左の Project Navigator 上でプロジェクトを選択
- 右側に表示された TARGETS 欄のターゲットを選択して、コンテキストメニューを表示する
- コンテキストメニューから Duplicate を選択
- Duplicate iPhone Target というダイアログが表示されるので Duplicate Only ボタンを押す
これらの手順を実行すると...
- TARGETS 欄に「元のターゲット名 copy」というターゲットが追加される
- Project Navigator 上に「元のターゲット名 copy-info.plist」が追加される
追加されたターゲットは元の設定を複製したものとなる。ただしこの時点では「元のターゲット名 copy」という名前が随所に使用されているため分かりやすいものに変更したほうがよい。対象は以下。
- Project Navigator 上の .plist ファイル
- TARGETS 欄のターゲット
- Build Settings の Packaging 欄における info.plist File と Porduct Name
- ビルド設定の Scheme
ターゲット追加と名前の変更が完了したら正しく反映されていることを確認。Xcode 左上の Scheme 部分をクリックするとターゲットを切り替えられるので、それぞれをデバッグ実行できるかチェックしてみよう。設定が適切なら個別のアプリとしてインストール & 実行されるはず。
ターゲットごとにリソースを分岐する
複数のターゲットを用意したので組み込むリソースを分岐してみる。
Target Membership
プロジェクト内に複数のターゲットが存在するならソースコードや各種ファイルをどちらに組み込むのか指定する必要がある。例えば AppDelegate クラスは共通、リソースは必要に応じて分岐することになるだろう。この設定は Target Membership でおこなう。Xcode 右上の View 欄から Utilities を選び File inspector タブを開くと表示される。
この欄にはプロジェクト内のターゲットがリスト表示される。項目のチェック ボックスを有効にするとそのターゲットにファイルが組み込まれる。外せば除外。
UIViewController
などを新規追加するとデフォルトではオリジナルのターゲットだけ有効になっている。ターゲット間で共有する場合は必ず他の項目もチェックすること。対象ファイルが必要とされるとき、そのターゲットに組み込まれていないとアプリが不正終了するなどの問題が起きる。
リソースの分岐
リソースのターゲットによる分岐を理解するため、実際にリソースとプロジェクト構成を設定する。共通の背景上にターゲット別の写真を表示するサンプルを作成してみよう。
プロジェクトのフォルダ内に res というサブフォルダを作成。GUI パーツ画像などターゲット間で共有するリソースはここに置く。ターゲットごとのファイルは個別にサブフォルダを用意してその中へ格納する。今回は Original と Sub の 2 種類を作成した。
Interface Builder やプログラムからリソースを参照する場合は GUI や API には対象ファイルの名前を指定することになる。よってターゲットごとに内容が分岐するものはフォルダを分けつつ同一名にしておくと処理を共通化できて便利。
res 直下へ background.png という背景用テクスチャ、サブフォルダの Original と Sub 内へ picture.jpg という写真画像を配置する。後者についてはパブリックドメインの写真を提供するサービス Pixabay から以下の作品を利用させていただいた。
次に Xcode でプロジェクトを開き Project Navigator の Supporting Files 以下に res フォルダと同じ構成でグループを作成。それぞれのグループ配下に画像ファイルを追加して Target Membership を設定。res 直下のファイルは全ターゲット共通、Original と Sub はターゲットごとに分岐させておく。
この設定により background という名前で画像を参照した場合は共通、picture ならばターゲットごとに別の画像が読み込まれる。これらを表示する画面を実装してターゲットを切り替えてからアプリを実行してみると分岐されていることを確認できるはず。左は Original、右は Sub となる。
リソースをローカライズする場合はそれぞれのフォルダ内に言語別フォルダが作成されることになる。Xcode 上でファイルに言語指定した場合、元ファイルのフォルダ内へ自動的に言語別フォルダを生成してくれる。そのためこの構成をそのまま利用しておくと管理しやすい。
アプリ名の分岐
アプリ名を明示的に指定したいなら InfoPlist.strings に CFBundleDisplayName を設定する。
/* Localized versions of Info.plist keys */
CFBundleDisplayName = "Application Name";
InfoPlist.strings はターゲットごとに一つ、ローカライズするなら言語の数だけ分岐という感じで管理するのと分かりやすい。ここでは「ターゲット x 言語 (en/ja)」を定義してみる。
はじめにプロジェクトから InfoPlist.strings を除外する。Project Navigator 上で削除するときのダイアログでは Remove References を選択してファイルは残すこと。次に InfoPlist.strings を res/Original と res/Sub 内へコピーする。コピーし終えたら元ファイルは削除しておく。これらのファイルをプロジェクトに取り込みターゲットと言語を設定する。
この状態でそれぞれの InfoPlist.strings に異なる CFBundleDisplayName を設定してからアプリを起動するとホーム画面のアプリ名が分岐していることを確認できるはず。
アイコンとスプラッシュ画像の分岐
アイコンとスプラッシュ画像についてもアプリ名と同じ分岐方法となる。ターゲット単位で規定の名前 (Icon.png や Default.png など) で画像を用意してプロジェクトへ取り込み Target Membership を設定する。
Xcode の TARGETS 欄からターゲットを選択して Summary を開く。App Icons 欄と Launch Images 欄がある。画像を変更したい項目のコンテキストメニューから Select File を選びターゲットにあった画像を参照。
適切に指定できていればホーム画面のアイコンとアプリ起動時のスプラッシュ画像が変更されるはず。反映されないなら実行環境でキャッシュが効いている可能性があるため、確実に反映させたいなら以下の手順を実行する。
- 実行環境からアプリを削除
- Xcode のメニューから Product - Clean を選択、これはターゲットそれぞれに対して実行すること
- Xcode からアプリを実行
以下は適切にアイコンが分岐された状態のホーム画面。
サンプル プロジェクト
今回の調査で作成したサンプル プログラムのプロジェクト一式は GitHub で公開している。
開発は Xcode 4.6.3、動作確認には iPod touch 第 5世代 (iOS 6.1.3) と iPhone 6.1 シミュレーターを使用。ライセンスは The MIT License (MIT)。fork、改変などはご自由にどうぞ。