3/3

2026

自分のアプリを「ユーザーとして」楽しむための専用ターゲット

#Xcode#iOS#SPM#AI#pbxproj#TestFlight自分のアプリを「ユーザーとして」楽しむための専用ターゲット

自分のアプリを「ユーザーとして」楽しむための専用ターゲット

自分が作ったアプリを、開発者としてではなく一人のユーザーとして使いたい。開発ビルドだとデバッグログが流れるし、テスト用の設定が残っていて純粋に楽しめない。PlayTimeの場合、ボス協力機能の動作確認中にうっかりテストデータを上書きしたりする。

そこで開発と完全に分離した自分専用ターゲットを作ることにした。別のBundle IDでTestFlight1配信すれば、開発ビルドとは独立した「ユーザー版」を自分の端末に入れられる。

やるべきことは以下の通り。

  1. Xcode GUIでターゲットをDuplicate(メインアプリ・Watch App・Watch Widget Extensionの3つ全て)
  2. Bundle IDを専用のものに変更
  3. App Groupを新ターゲットにも登録
  4. ハードコードされたBundle ID・App Group名をplist/Bundle APIから動的取得に変更
  5. アイコンにバナー等を追加して開発版と視覚的に区別

必要なこと

それぞれ詳しく見ていく。

1. ターゲット複製 — GUI Duplicateが正解

Xcodeでターゲットを右クリック → 「Duplicate」。これだけで以下が全てコピーされる。

  • SPM2パッケージ依存
  • Embed Frameworks3 Build Phase(CodeSignOnCopy4フラグ含む)
  • Capabilities(Push Notifications、Keychain Sharing等)
  • Build Settings、Build Phases全て

Watch Appとその拡張も含めて、依存関係ごと正しく複製される。PlayTimeForMeの場合、メインアプリ・Watch App・Watch Widget Extensionの3ターゲット全てをDuplicateする必要がある。

💡 手順: (1) Xcode GUIでDuplicate → (2) Bundle ID変更 → (3) Display Name変更 → (4) Info.plist編集。構造変更はGUI、テキスト変更はエージェントに任せるのが安全。

2. Bundle ID分離

専用ターゲットには本番とは異なるBundle IDを設定する。

  • 本番: jp.po-miyasaka.PlayTime
  • 専用: jp.po-miyasaka.PlayTimeForMe

Bundle IDが別なので、TestFlightで本番版と専用版を同じ端末に共存できる。App Store Connectにも別アプリとして登録される。

3. App Group設定

PlayTimeではWidget Extensionとのデータ共有にApp Group5を使っている。専用ターゲットでもWidgetを動かすなら、App Groupの設定が必要。

  • 専用ターゲットのentitlementsにApp Groupを登録
  • Developer PortalでApp Group IDを作成・紐づけ

これを忘れるとRealmのDBやUserDefaults6の共有が機能しない。データは保存されるように見えても、再起動で消える(App Groupコンテナにアクセスできず、メモリのみで動作するため)。

4. ハードコード値の動的取得

コード内にBundle IDやApp Group名をハードコードしていると、ターゲットを変えた途端に壊れる。

// NG: ハードコード
static let groupID = "group.jp.po-miyasaka.PlayTime"

// OK: Info.plistから動的取得
static var groupID: String {
    Bundle.main.object(forInfoDictionaryKey: "AppGroupIdentifier") as? String
        ?? "group.jp.po-miyasaka.PlayTime"
}

Bundle ID、App Group名、API Key等、ターゲット固有の値はInfo.plist7Bundle.mainのAPIから取得する設計にしておく。ターゲットごとにplistの値を変えるだけで対応できるようになる。

5. アプリアイコンの区別

Bundle IDを分けると同じ端末に本番版と専用版が共存する。ただしアイコンが同じだと、ホーム画面でどちらを開いているか分からない。

PlayTimeForMeでは、アイコンに青い「ForMe」バナーを追加して視覚的に区別した。Assets.xcassets8にAppIcon-ForMeとしてForMeターゲット専用のアイコンセットを配置し、ターゲットのBuild SettingsでApp Icon Assetを切り替える。

開発版(PlayTime)ForMe版(PlayTimeForMe)
開発版アイコンForMe版アイコン

ForMe版は下部に青い「ForMe」バナーが入っている。ホーム画面で一目でどちらか分かる。

技術的な分離(Bundle ID、App Group等)だけでなく、日常的に使う上で混乱しないUXレベルの工夫も必要だった。

つまずいたポイント

実際にはすんなりいかなかった。事実だけ記録しておく。

1. pbxproj9テキスト編集によるdyldクラッシュ

AIエージェントにpbxprojを直接テキスト編集させてターゲットを作成した。ビルドは通ったが、Embed Frameworks3 Build Phaseのコピーが漏れていてdyld: Library not loaded: @rpath/RealmSwift.framework/RealmSwift10で起動クラッシュ。pbxprojは数千行の独自フォーマットで、SPM依存・フレームワークリンク・Build Phases・Build Settings等が複雑に絡み合っている。AIがテキスト編集で全ての依存関係を正しくコピーするのは困難。GUI Duplicateならこれら全てが自動でコピーされるため、この問題は起きない。

2. App Group名ハードコードによるデータ消失

RealmService.swiftにApp Group名がハードコードされていた。専用ターゲットにはそのApp Groupが未登録だったため、Realmがメモリのみで動作し、再起動でデータが全消失。entitlementsへのApp Group登録と、plistからの動的取得に修正。

3. フォールバック修正の罠

上記2の修正としてエージェントが出してきたのは「App Groupが使えなければDocumentsにフォールバック」。動作はするが、Widget連携が壊れる・ForMe版だけ劣化した動作になるなど本質的に間違い。正しくはentitlementsにApp Groupを登録し、ハードコードをplist動的取得に変更すること。

まとめ

開発用ビルドと分離した自分専用ターゲットを作るために必要なこと。

作業ポイント
ターゲット複製Xcode GUI Duplicateで行う。メインアプリ+Watch App+Widget Extensionの全ターゲットが対象
Bundle ID本番と別のIDを設定。TestFlightで共存可能に
App Group専用ターゲットのentitlementsにも登録。Widget連携に必須
ハードコード排除Bundle ID・App Group名等はplist/Bundle APIから動的取得
アイコン区別ForMe版に専用バナーを追加。ホーム画面で一目で判別可能に

プロジェクト構造の変更(ターゲット追加・SPM操作・Capabilities)はGUIで行い、テキストレベルの設定変更(Bundle ID・plist編集等)はエージェントに任せる。この使い分けが安定する。

Footnotes

  1. TestFlight — Appleが提供するベータテスト配信サービス。App Store Connectにアップロードしたビルドを、招待したテスターの端末にOTA配信できる。

  2. SPM (Swift Package Manager) — Apple公式のパッケージ管理ツール。Xcode 11以降で統合されており、Package.swiftで依存関係を定義する。

  3. Embed Frameworks Build Phase — Xcodeのビルドフェーズの1つ。動的フレームワークをアプリバンドルのFrameworks/ディレクトリにコピーする。これがないと実行時にdyldがフレームワークを見つけられない。 2

  4. CodeSignOnCopy — Embed時にフレームワークを再署名するオプション。アプリ本体の署名と一致させるために必要。

  5. App Group — 同じ開発者の複数アプリ・Extension間でデータを共有するためのiOS/macOSの仕組み。共有コンテナとUserDefaultsの共有に使う。

  6. UserDefaults — iOS/macOSの軽量キーバリューストア。UserDefaults(suiteName:)でApp Group経由の共有UserDefaultsにアクセスできる。

  7. Info.plist — iOSアプリのメタデータファイル。Bundle ID、バージョン、権限宣言等に加え、カスタムキーを追加してターゲット固有の値を格納できる。

  8. Assets.xcassets — Xcodeのアセットカタログ。アプリアイコン、画像リソース等を管理する。ターゲットごとに異なるApp Iconセットを指定できる。

  9. pbxprojproject.pbxprojファイル。Xcodeプロジェクトの中核。ターゲット、ビルド設定、ファイル参照、Build Phase等の全情報を独自フォーマットで格納している。

  10. @rpath — 実行時検索パス。dyldが動的ライブラリを探す際のベースパス。iOSアプリでは@executable_path/Frameworksが設定される。