iOSアプリを作って公開するまで — Firebase・サインイン・課金・審査提出の実践ガイド

個人開発でiOSアプリを1本作り、Firebaseでバックエンドを持たせ、Googleログイン等の複数プロバイダでサインインできるようにし、RevenueCatでサブスク課金を組み込み、最終的にApp Store Connectへ提出するまでの一連の作業をまとめたものです。特定のアプリの話ではなく、次に別のアプリを作るときにも使える一般的な手順・考え方として書いています。

実際に手を動かした人間が「これは何?」「なぜこの順番?」とつまずいた箇所をできるだけそのまま拾って、先回りして説明する形式にしています。ある程度iOS開発の経験がある人(Swift/SwiftUIでアプリを組める人)が対象で、Xcodeの基本操作そのものは説明しません。

⚠️ 前提: このガイドはMac専用です

ここに書く作業はすべて Xcode を使います。Xcodeは macOS 専用のアプリで、Windows版・Linux版は存在しません(Appleが提供していません)。したがって、

  • iOSアプリのビルド・署名・App Store Connectへのアップロードは Macが無いと物理的にできません
  • 「WindowsにXcodeをインストールする方法」を探しても正規の方法は存在しません(仮想化やクラウドMacサービスなど回避策はありますが、本ガイドの範囲外です)
  • Firebaseコンソールの設定作業や、RevenueCat/App Store Connectのダッシュボード操作自体はブラウザで完結するのでWindowsからでも可能ですが、実際にアプリをビルドして提出する最終工程には必ずMacが要る、という点だけ最初に押さえておいてください

目次

  1. 全体の流れ
  2. Firebaseプロジェクトの作成と組み込み
  3. 複数プロバイダでのサインイン実装(Apple / Google / Microsoft)
  4. RevenueCatによるサブスク課金の組み込み
  5. Apple Developer Portal / App Store Connectの初期設定
  6. Xcodeでのアーカイブ・署名・アップロード
  7. アップロード後の流れ: TestFlightと審査提出は別操作
  8. よくあるワナ(FAQ)

1. 全体の流れ

まず全体像です。工程が多いので、迷子にならないよう最初に地図を示します。

[Firebaseプロジェクトを作成し、アプリに組み込む]
        ↓
[Sign in with Apple / Google / Microsoft を実装する]
        ↓
[RevenueCatダッシュボードで課金商品を設定]
        ↓
[App Store Connectで課金商品(Product)を作成し、RevenueCatと連携]
        ↓
[アプリにRevenueCat SDKを組み込み、エンタイトルメントで機能をゲーティング]
        ↓
[Apple Developer PortalでApp IDと各種Capabilityを作成]
        ↓
[Xcode: Product → Archive]
        ↓
[Organizer: Distribute App → App Store Connect → Upload]
        ↓
[App Store Connect側でビルドの処理完了を待つ(メール通知が来る)]
        ↓
[TestFlightでの配布 or 実際に審査へ提出(Submit for Review)※ここは別操作]

最初に強調しておきたいのは、「アップロードが成功した」≠「審査に出した」 という点です。アップロードは単にビルドをApp Store Connectに登録するだけの操作で、TestFlightでの配布やストア審査への提出は、アップロード後にApp Store Connect側で明示的に行う別の操作です。ここを混同すると「アップロードしたのにいつまで経っても審査が始まらない」と不安になりがちなので、最初に区別しておいてください。

また、Firebase・サインイン・課金は互いに依存関係があります(例: RevenueCatにユーザーを紐付けるにはFirebase Authでのサインインが先に必要)。上の順番はその依存関係に沿ったものなので、基本的にはこの順で進めるのがスムーズです。


2. Firebaseプロジェクトの作成と組み込み

2.1 なぜFirebaseを使うのか

自前でサーバーを立てずに、認証(サインイン)・データベース・サーバー経由でのAPI呼び出し保護(App Check)といった「バックエンドが要る機能」をまとめて使えるのがFirebaseの利点です。個人開発でサーバーの運用まで背負いたくない場合、最初の選択肢として妥当です。この後の章(サインイン)は Firebase Authentication という機能を使います。

2.2 プロジェクト作成とアプリ登録

  1. Firebaseコンソール で新規プロジェクトを作成する
  2. プロジェクト内に「iOSアプリ」を追加する。ここで Bundle ID(例: com.example.myapp)の入力を求められます。これはXcode側のプロジェクト設定(Signing & Capabilities)で使っているBundle IDと完全に一致させる必要があります。あとから変えるのも可能ですが、両者がズレていると各種機能が正しく動かないので、最初にどちらかを確定させてから他方を合わせるのがおすすめです
  3. GoogleService-Info.plist というファイルがダウンロードできるようになります。これがFirebaseプロジェクトとアプリを結びつける設定ファイルで、プロジェクトID・APIキー・Bundle IDなどが入っています。これをXcodeプロジェクトのアプリターゲット直下(他のリソースファイルと同じ階層)に配置し、ターゲットに含める形でXcodeプロジェクトに登録します

これは何?: GoogleService-Info.plist は「秘密鍵」ではありません。中に入っているAPIキーはクライアントアプリに埋め込まれる前提の識別子で、サーバー側の認可(Firestoreのセキュリティルールや、後述のApp Check)と組み合わせて安全性を担保する設計になっています。とはいえBundle ID等のプロジェクト固有情報を含むため、公開リポジトリに無警戒にコミットするのは避けた方が無難です。

  1. XcodeプロジェクトにFirebase SDKをSwift Packageとして追加する。パッケージURLは https://github.com/firebase/firebase-ios-sdk で、使う機能に応じて FirebaseAuth(サインイン)、FirebaseCore(共通基盤、他のFirebase機能を使うなら必ず必要)などのプロダクトを選んで追加する

2.3 FirebaseApp.configure() の初期化順序に注意

Firebaseの各機能(Auth等)を使う前に、必ず1回 FirebaseApp.configure() を呼んでおく必要があります。これを呼ぶ前にFirebase Authの関数(例: Auth.auth())に触れると実行時クラッシュします。

これ自体は単純なのですが、実際につまずきやすいのはアプリの初期化順序が思ったとおりに保証されない場合があることです。たとえばSwiftUIの @StateObject のデフォルト値式は、宣言順によってはApp構造体の init() より先に評価されることがあります。もし複数の場所(App構造体の init() と、認証まわりのクラスの init() の両方など)からFirebaseの初期化に触れる可能性があるなら、

private static var didConfigureFirebase = false

static func configureFirebaseIfNeeded() {
    guard !didConfigureFirebase else { return }
    FirebaseApp.configure()
    didConfigureFirebase = true
}

のように冪等な初期化関数にしておき、「どこから最初に呼ばれても安全」な状態にしておくと事故が減ります。原因不明のクラッシュが起動直後に起きた場合、まずこの初期化順序を疑ってみてください。

2.4 App Check(サーバー経由のAPI呼び出しを保護したい場合)

もしアプリからFirebase経由で外部API(生成AIのAPIなど)を呼ぶ設計にする場合、App Check を使うとAPIキーをアプリのバイナリに一切含めずに済みます。仕組みはこうです。

[アプリ] --①--> [Firebase (サーバー)] --②--> [外部API]
  1. アプリはAPIキーを持たず、Firebase SDK経由でリクエストを送る
  2. Firebase側がプロジェクトに紐づくAPIキーで実際のAPIを呼び出し、結果だけをアプリへ返す
  3. すべてのリクエストに App Check(iOSでは App Attest)のトークンが付与され、Firebase側で「本物のこのアプリの、改造されていないインスタンスからの通信か」を検証してから②へ進む

設定は Firebaseコンソール「Project Settings」→「App Check」で、対象アプリのプロバイダに「App Attest」を選ぶだけです。導入初期は「モニターのみ」モードで様子を見て、正常に通信できることを確認してから「強制」に切り替えるのが安全です。

注意: App Checkは「本物のアプリからの通信か」は検証しますが、「そのユーザーが課金しているか」までは検証しません。課金状態のチェックは別レイヤー(後述のRevenueCatエンタイトルメント判定)で行う必要があります。この2つは役割が違うので混同しないでください。


3. 複数プロバイダでのサインイン実装(Apple / Google / Microsoft)

3.1 なぜ複数プロバイダを用意するのか、Appleだけで良いのか

App Storeのガイドラインでは、他のサードパーティ製サインイン手段(Googleログイン等)を提供する場合、Sign in with Appleも同等の選択肢として提供する必要があります(逆に、Appleサインインだけを提供するのは問題ありません)。つまり「Googleでサインインだけ」は審査で指摘される可能性がありますが、「Appleでサインインだけ」は問題ない、という非対称なルールです。ユーザー層にAndroid/Web由来のGoogleアカウントユーザーが多いなど、Apple以外の選択肢を用意したい理由がある場合に、Appleも並べて提供する、という組み合わせが典型です。

3.2 バックエンドはFirebase Authに一本化する

Apple・Google・Microsoftそれぞれ生の認証プロトコルが微妙に異なりますが、Firebase Authenticationが3つとも「プロバイダ」として抽象化して扱ってくれます。最終的にどのプロバイダでサインインしても、アプリ側は同じ Auth.auth().signIn(with: credential) という1つのAPIに帰着し、Auth.auth().currentUser で統一的にログイン状態を扱えます。以降、各プロバイダの認証情報を「Firebaseが認識できる AuthCredential に変換するところまで」がプロバイダごとの個別実装、その先は共通、という構造で理解すると見通しが良くなります。

3.3 Sign in with Apple

Apple Developer Portal側の準備: App IDに「Sign In with Apple」のCapabilityを有効化し、Xcode側のターゲットにも同名のCapability(Entitlement)を追加します。

Firebaseコンソール側の準備: 「Authentication」→「Sign-in method」→「Apple」を有効化します。ネイティブのiOSアプリ内で完結するフローであれば、Webサインイン用の追加設定(Services ID等)は不要です。

コード側の実装のポイント:

  • ASAuthorizationController を直接使う実装にすると、SwiftUIの SignInWithAppleButton を使う場合よりも柔軟に呼び出しタイミングを制御できます(例: アカウント削除の直前に再認証だけ単独で走らせる、といった用途)
  • nonce(ワンタイムトークン)によるリプレイ攻撃対策が必須です。リクエスト開始時にランダムな文字列を生成してSHA256でハッシュ化したものをAppleへのリクエストに載せ、認可が返ってきた後、元のnonceをFirebase側の検証に渡します。この2段構え(生のnonceとハッシュ化したnonce)を混同しないよう注意してください
  • 氏名・メールアドレスの requestedScopes は、本当に必要な場合以外はリクエストしないことを推奨します。Appleは氏名・メールを初回の認可時にしか返しません。2回目以降の再認証では(同じApple IDでも)返ってこないため、「アプリ側に保存し忘れると二度と取得できない」「保存していても、アプリ削除→再インストール後に再度サインインすると”消えた”ように見える」という罠があります。これらの情報を業務要件で使わないなら、最初から requestedScopes = [] にしてしまうのがシンプルです

3.4 Google Sign-In

GoogleについてはFirebaseの汎用OAuthProvider経由ではなく、Google公式のネイティブSDK(GoogleSignIn-iOS)を使うことが公式に推奨されています。Swift Packageとして https://github.com/google/GoogleSignIn-iOS を追加してください。

設定のポイント: Googleサインイン用のクライアントIDは、Firebaseコンソールで「Authentication」→「Sign-in method」→「Google」を有効化すると自動的に発行され、更新済みの GoogleService-Info.plist に含まれます(FirebaseApp.app()?.options.clientID で取得可能)。GCP側のOAuth同意画面を別途手動設定する必要は、シンプルな用途では基本的にありません。

コード側の実装のポイント:

GIDSignIn.sharedInstance.configuration = GIDConfiguration(clientID: clientID)
let result = try await GIDSignIn.sharedInstance.signIn(withPresenting: presentingViewController)
let credential = GoogleAuthProvider.credential(
    withIDToken: result.user.idToken!.tokenString,
    accessToken: result.user.accessToken.tokenString
)
let authResult = try await Auth.auth().signIn(with: credential)

signIn(withPresenting:) は表示元となる UIViewController を要求します。SwiftUIオンリーのプロジェクトだと「そもそもViewControllerってどこから取るの?」となりがちですが、現在アクティブなウィンドウシーンから rootViewController を取得すれば十分です。また、サインアウト処理では Auth.auth().signOut() に加えて GIDSignIn.sharedInstance.signOut() も忘れずに呼ぶ必要があります(Firebase側だけサインアウトしても、Google SDK側にトークンが残っていると次回サイレントサインインしてしまうことがあります)。

3.5 Microsoft Sign-In

Microsoftについては追加のSDKは不要です。Firebase Authが提供する汎用の OAuthProvider を使い、ASWebAuthenticationSession ベースの対話フロー(ブラウザのようなシートが一瞬開いてサインインする方式)をFirebase側が内部で処理してくれます。

let provider = OAuthProvider(providerID: "microsoft.com")
let credential = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<AuthCredential, Error>) in
    provider.getCredentialWith(nil) { credential, error in
        if let error {
            continuation.resume(throwing: error)
        } else if let credential {
            continuation.resume(returning: credential)
        }
    }
}
let authResult = try await Auth.auth().signIn(with: credential)

事前準備が他の2つより1段階多い点に注意してください:

  1. Azure Portal で「アプリの登録」を行い、クライアントID・クライアントシークレットを発行する
  2. リダイレクトURIとして、Firebaseが指定する形式(https://<プロジェクトID>.firebaseapp.com/__/auth/handler など、Firebaseコンソールの案内に従う)を登録する
  3. Firebaseコンソール「Authentication」→「Sign-in method」→「Microsoft」を有効化し、発行したクライアントID・シークレットを入力する

この3ステップを飛ばして「コードだけ書けば動くはず」とハマるケースがあるので、MicrosoftだけはAzure側の管理画面作業が先に必要だと覚えておいてください。

3.6 サインアウトとアカウント削除

サインアウトは各プロバイダのSDKのサインアウト処理(Googleのみ追加対応が必要、上述)に加えて Auth.auth().signOut() を呼びます。

アカウント削除(user.delete())については、Firebaseは破壊的操作に**「直近の再認証」**を要求する仕様になっています。しばらく前にサインインしたユーザーがいきなりアカウント削除しようとすると requires-recent-login のようなエラーになることがあるため、削除操作の直前に該当プロバイダでの再サインインを挟んでから削除を呼ぶ、という2段構えの実装にしておくと安全です。

また、iOSアプリで何らかのアカウント登録・サインインを必須にする場合、App Store審査ガイドライン5.1.1(v)により、アプリ内から自分でアカウントを削除できる導線の提供が実質必須です(外部サイトへの誘導だけでは不十分とされるケースがあります)。サインインを実装する際は、削除機能もセットで計画に入れておいてください。


4. RevenueCatによるサブスク課金の組み込み

4.1 なぜRevenueCatを使うのか

StoreKit(Appleの課金フレームワーク)を直接叩いて、レシート検証・サブスクの更新状態管理・複数プラットフォーム対応まで自前で作るのは労力が大きい作業です。RevenueCatはこれを肩代わりしてくれるサービスで、無料枠内で個人開発規模なら十分に使えます。

4.2 依存の追加

Xcodeプロジェクトに Swift Package として https://github.com/RevenueCat/purchases-ios-spm.git を追加します。プロダクトは2つ追加するのがおすすめです。

  • RevenueCat — SDK本体
  • RevenueCatUI — Apple公式デザインの PaywallView 等、既製のペイウォールUIコンポーネント

4.3 RevenueCatダッシュボード側の設定

  1. RevenueCatダッシュボードでプロジェクトを作成し、App Store向けのAppを1つ登録する
  2. Entitlement(例: premium)を1つ以上作成する。エンティトルメントは「ユーザーが今どの権利を持っているか」を表す抽象的な単位で、複数の商品(月額・年額等)を同じエンティトルメントに紐付けられる
  3. ProductをApp Store Connect側(後述4.4)で作成した後、RevenueCat側でも同じProduct IDを登録し、エンティトルメントに紐付ける
  4. Offering(ユーザーに見せる商品の組み合わせ、通常は”default”の1つで足りる)を作成し、Productを含める
  5. 「Project Settings → API Keys」から公開APIキーappl_で始まる文字列。App Store用)を取得する。このキーはアプリバイナリに埋め込む前提の「公開」キーであり、秘密鍵ではありません
  6. 「Project Settings → Integrations → App Store Connect」で、App Store ConnectのAPIキー(またはApp共有シークレット)を登録し、RevenueCatがサーバーサイドでレシート検証・購入状態同期を行えるようにする

4.4 App Store Connect側での商品(Product)作成

App Store Connect → 対象アプリ → 「App内課金」または「サブスクリプション」から、実際に販売する商品を作成します。サブスクリプション・非消費型・消費型のいずれにするかは商品設計次第です。ここで作ったProduct IDを、4.3の手順3でRevenueCat側にも登録します。

審査に商品情報も含める: サブスクリプション/App内課金は、アプリ本体とは別に商品自体の審査も必要になる場合があります(価格・表示名・スクリーンショット等)。アプリの初回審査に商品を同梱して出すか、後から追加するかは早めに方針を決めておいてください。

4.5 コード側の実装のポイント

  • Purchases.configure(withAPIKey:) の呼び出しタイミング: Firebase等、他のSDKの初期化順序に依存する場合は要注意です(2.3節と同じ理由)。「どちらが先に呼ばれても安全」なように初期化処理自体を冪等にしておくのが無難です
  • エンタイトルメントでのゲーティング: 個別の商品IDではなく customerInfo.entitlements["premium"]?.isActive のようにエンティトルメントIDで判定します。これにより将来商品構成を変えてもコード側は変更不要になります
  • PaywallView()(RevenueCatUI): .sheet 等で表示するだけでApple公式デザインの購入画面が出ます。オファリングの中身(価格・訴求文言)はRevenueCatダッシュボード側で編集でき、アプリのアップデート無しで変更できます
  • アカウントとの紐付け(logIn/logOut: 3章で実装した認証システム(Firebase Auth)を持つ場合、サインイン時に Purchases.shared.logIn(firebaseUID) を呼んでRevenueCat側のappUserIDを紐付け、サインアウト時は logOut() で匿名IDに戻します。これをしないと機種変更・再インストール後にサブスク状態が引き継がれません。呼び出す順序は「RevenueCatの delegate 設定直後、オファリング等を取得する前」が推奨です
  • PurchasesDelegate: purchases(_:receivedUpdated:) でサーバー側の状態変化(他デバイスでの購入・解約等)をリアルタイムに反映します

4.6 ペイウォール(購入画面)の作り方

課金機能で一番「何をどう作ればいいのか分からない」となりやすいのが、ユーザーに実際に見せる購入画面(ペイウォール)です。ここは2つの作り方があります。

方法A: RevenueCatのPaywall Builder(コード不要、まずはこちらを推奨)

RevenueCatダッシュボードの対象Offering画面に「Paywalls」タブがあり、テンプレートを選んでドラッグ&ドロップ的にテキスト・画像・配色を編集するだけで購入画面を作れます。アプリ側のコードは

PaywallView()
    .sheet(...)

のようにRevenueCatUIの PaywallView() を表示するだけで、ダッシュボードで作った内容がそのまま反映されます。購入画面のコピーや価格訴求文言を変えるたびにアプリをアップデートしてストア審査を通す必要がないのがこの方式最大の利点です。1つのOfferingに複数のペイウォールを紐付けてA/Bテストすることもできます。

方法B: 自前のSwiftUI画面

デザインを完全に自由にしたい場合は、Purchases.shared.offerings() で取得した Package の配列(価格・期間などの情報を含む)を使って自分でUIを組み、選択されたPackageに対して Purchases.shared.purchase(package:) を呼ぶ形で実装します。方法Aで足りない場合の選択肢、という位置づけで問題ありません。

どちらの方法でも外せない必須要素(Appleガイドライン3.1.2)

購入画面には、サブスクリプションの期間・価格・自動更新される旨・利用規約(EULA)へのリンク・プライバシーポリシーへのリンク・「購入を復元」ボタンを必ず表示する必要があります。これらのどれか1つでも欠けていると、審査で「Guideline 3.1.2 – Business – Payments – Subscriptions」として差し戻される典型パターンになります。RevenueCatのPaywall Builderのテンプレートはこれらの要素をあらかじめ含んだ状態で提供されていますが、自前でデザインし直す場合は消してしまわないよう注意してください。

「購入を復元」ボタンは、機種変更やアプリの再インストール後に新規課金なしで以前の購入状態を取り戻すための導線です。Purchases.shared.restorePurchases() を呼ぶだけの実装ですが、ボタン自体を置き忘れるケースが多いので明示的にチェックリストに入れておいてください。

4.7 シミュレータ・実機でのテスト

  • シミュレータ: StoreKitのSandbox購入フローはシミュレータでも一応動きますが、実際の課金導線を毎回通すのは開発効率が悪いです。デモ用の起動引数(例: --demo-subscribed)を用意し、購入済み状態を強制的にtrueにするデモモードを作っておくと開発が速くなります(本番ビルドの通常起動では絶対に有効にならないよう、デバッグビルドかどうかのガードを必ず入れてください
  • 実機(Sandbox環境): App Store Connect → 「ユーザーとアクセス」→「Sandboxテスター」でテスト用Apple IDを作成し、実機の設定でサインインすると実際の購入フローを(課金されずに)通しでテストできます

5. Apple Developer Portal / App Store Connectの初期設定

初めてこのアプリを提出する場合の、初回のみの設定です。ここは工程数が多く、かつ「アプリ本体のコード」と直接関係ない管理画面作業が中心になるため、一つずつ順を追って説明します。

5.1 Apple Developer Portal: App IDとCapability

  1. Apple Developer Portal → 「Certificates, Identifiers & Profiles」→「Identifiers」で新規App IDを作成します(Bundle ID、例 com.example.myapp
  2. Widget拡張・Watchアプリ等、複数ターゲットがある場合はそれぞれ別のBundle IDでApp IDを作ります(例: com.example.myapp.MyWidget)。「本体アプリのBundle IDの後ろにサフィックスを足す」命名にしておくと、後で見たときに親子関係が分かりやすくなります
  3. 使用する機能に応じたCapability(Sign In with Apple、App Groups、Push通知、Background Modes等)をここで有効化します。アプリ側のXcodeプロジェクトでCapabilityを追加しただけでは不十分で、Developer Portal側のApp IDにも同じCapabilityが有効化されている必要があります。両者がズレていると、Xcodeでの署名時に「プロビジョニングプロファイルにこのCapabilityが含まれていません」という趣旨のエラーが出ます

5.2 App Store Connect: アプリレコードの新規作成

  1. App Store Connect → 「マイApp」→「+」→ 新規Appを作成します。Bundle IDはDeveloper Portalで作った本体アプリのものを選びます(拡張機能・Watchアプリは本体に自動的に紐づきます)
  2. この時点で「App名」(ストアに表示される正式名称、App Store全体でユニークである必要がある)とプライマリ言語を決める必要があります。App名は後から変更できますが、同名の別アプリが既に存在すると使えません

5.3 証明書・プロビジョニングプロファイル

Xcodeの「Automatically manage signing」を使えば、アーカイブ・アップロード時にXcodeがApple Developer Services APIを通じて自動生成・取得してくれます。手動でPortal上から証明書やプロビジョニングプロファイルを作る必要は基本的にありません。個人開発であればこの自動管理に任せるのが一番トラブルが少ないです。

5.4 アプリ情報(App Information)とプライシング

App Store Connectのアプリレコード内、「App情報」タブで以下を設定します。

  • カテゴリ(プライマリ/セカンダリ): ストアでの分類先。後からでも変更可能
  • コンテンツ配信権: 自分がコンテンツの権利者かどうかの申告
  • App Storeでの表示名・サブタイトル・プロモーション用テキスト: サブタイトルはアプリ名の下に出る短い説明文で、こちらも後から変更可能

「価格および配信状況」タブでは、価格帯(無料アプリでも設定は必要)と配信対象の国・地域を選びます。サブスクリプションを提供するアプリであっても、アプリ本体のダウンロード自体は無料に設定するのが一般的です(アプリ内課金として別立てで課金するため)。

5.5 App Privacy(プライバシー情報の申告)— 提出前に必須

「App Privacy」タブで、アプリが収集するデータの種類と用途を申告するアンケートに回答します。これは通称「プライバシー・ニュートリションラベル」と呼ばれ、ストアのアプリページに表示される「このAppのプライバシー」欄の元データになります。

  • Firebase Authでサインインを実装している場合、メールアドレスやユーザーIDなどの識別子を収集している扱いになるため、正直に申告する必要があります
  • 「データの収集はしていません」と偽って申告し、実際には収集している(Firebase Analytics等が有効なだけでも該当しうる)ことが後から判明すると、審査差し戻しやアプリ削除の対象になります。使っているSDK(Firebase、RevenueCat、Google Sign-In等)のプライバシー影響については各社の公開情報を確認してから回答してください
  • この項目を未回答のまま審査に提出することはできません(提出ボタンがブロックされます)

5.6 年齢制限(Age Rating)

コンテンツの内容に応じた質問(暴力表現・ギャンブル要素・ユーザー生成コンテンツの有無等)に回答すると自動的にレーティングが算出されます。地図・位置情報アプリのような一般的な内容であれば、多くの場合4+に近い低いレーティングになりますが、SNS的な機能(他ユーザーとのコンテンツ共有等)を持つ場合は該当する質問に正直に答えてください。

5.7 サブスクリプション(Subscription Group)の作成

RevenueCat/App Store Connect双方の設定で触れた「4.4 App Store Connect側での商品(Product)作成」を、実際にはもう少し詳しい手順で行う必要があります。

  1. アプリレコード内「機能」→「App内課金」→「サブスクリプション」から、**Subscription Group(サブスクリプショングループ)**を新規作成します。グループは「同じ枠内で上位/下位プランの切り替え(アップグレード・ダウングレード)を許可する商品のまとまり」で、月額・年額のような期間違いのプランは同じグループに入れるのが一般的です
  2. グループ内に個別のサブスクリプション商品を追加します。各商品で入力が必要な項目:
    • 参照名(App Store Connect内部でのみ使う管理用の名前、ストアには表示されない)
    • Product ID(例: com.example.myapp.premium.monthly一度使ったIDは同じApp内で二度と再利用できないため、後から仕様変更する可能性を考えて命名は慎重に)
    • 価格: 「価格表」から選択。地域ごとの価格は基準価格から自動換算されますが、個別に上書きも可能
    • サブスクリプション表示名・説明文(最低1言語、ストアの購入画面に表示される)
    • レビュー用スクリーンショット: 初回のサブスクリプション審査時は、アプリ内で実際にこの商品が表示されている画面のスクリーンショットの添付が必須です。ここを忘れると審査に進めません
  3. RevenueCatダッシュボード側(4.3節)に戻り、ここで作ったProduct IDを登録してEntitlementに紐付けます

5.8 スクリーンショットと説明文(バージョンごとのメタデータ)

アプリレコード内、バージョンごとの編集画面で以下を用意します。

  • スクリーンショット: 提出するデバイスサイズ(iPhone・iPad等、対応させる画面サイズ)ごとに必要です。実機やシミュレータで撮影したものをそのまま使えますが、必要なサイズの組み合わせはApple側の要件が更新されることがあるため、提出時にApp Store Connectが要求してくるサイズに従うのが確実です
  • 説明文・プロモーション用テキスト・キーワード: 説明文はストア審査対象になる文章です。誇大広告的な表現(「No.1」等の裏付けのない優越表現)は指摘対象になりやすいので注意してください
  • サポートURL(必須)・マーケティングURL(任意): サポートURLは実際に開けるページである必要があります
  • プライバシーポリシーURL(必須): サブスクリプションを提供する場合、または何らかの個人データを収集する場合は必須です。Firebase Auth等でサインインを実装しているアプリはほぼ確実にこれに該当します。簡易的なものでも構わないので、事前に用意しておいてください

5.9 App Review Information(審査担当者への情報)— サインイン必須アプリは特に重要

「App Review Information」欄には、審査担当者がアプリを実際に操作してテストするために必要な情報を入力します。

  • 連絡先情報(電話番号・メールアドレス): 審査中に質問がある場合に使われます
  • デモアカウント: アプリの主要機能を使うのにサインインが必須な場合、審査担当者用のテストアカウント(ユーザー名・パスワード)を必ず用意してください。用意しないと、審査担当者がサインイン画面より先に進めず、その時点でリジェクトされます。3章で複数プロバイダのサインインを実装している場合でも、デモアカウントはどれか1つのプロバイダで確実にサインインできるものを用意すれば足ります
  • メモ欄: 審査担当者から見て動作が分かりにくい機能(位置情報を使う機能、バックグラウンド動作、サブスクリプションでゲーティングされている機能など)がある場合、ここに操作手順や補足説明を書いておくと誤解によるリジェクトを避けやすくなります

6. Xcodeでのアーカイブ・署名・アップロード

6.1 手順

  1. Xcode上部のスキーム選択で実機(”Any iOS Device”)を選ぶ(シミュレータが選択されているとArchiveメニューがグレーアウトします)
  2. Product → Archive
  3. ビルドが終わると Organizer が自動で開きます(開かない場合は Window → Organizer
  4. 対象のアーカイブを選び「Distribute App」→「App Store Connect」→「Upload」
  5. 署名方法(Automatically manage signing 推奨)を選んで進めると、アップロードが実行されます

6.2 プロジェクトをクラウド同期フォルダ(Google Drive等)で管理している場合の注意

プロジェクトフォルダがGoogle Drive・Dropbox・iCloud Driveなどのクラウド同期フォルダ配下にある場合、ビルド成果物の出力先(SYMROOT)を独自にその同期フォルダ配下へ向けるビルド設定は避けてください。同期プロセスがビルド中のバイナリに触れることでコード署名が壊れることがあります(Widget拡張のような組み込みバイナリを持つターゲットがあると特に顕在化しやすいです)。

対策はシンプルで、SYMROOTを独自指定せず、Xcodeの既定のスキームベースビルド(DerivedDataを使う、~/Library/Developer/Xcode/DerivedData 配下)のままにすることです。プロジェクトのソースコード自体はクラウド同期フォルダに置いたままで構いません。あくまで「ビルド成果物の出力先だけは同期対象から外す」のがポイントです。


7. アップロード後の流れ: TestFlightと審査提出は別操作

  1. アップロード直後、App Store Connect上のビルドは「処理中」表示になります。数分〜数十分後、Appleの自動処理(マルウェアスキャン等)が終わるとメールで通知が来て「有効」になります
  2. 処理完了後、TestFlightタブでそのビルドを内部/外部テスターに配布できます(審査不要、内部テスターのみなら即時)
  3. ストアでの公開には、「App Store」タブ側でそのビルドを選び、5章で用意したメタデータ(スクリーンショット・説明文・App Privacy・年齢制限・サブスクリプション情報・App Review Information)がすべて揃っていることを確認した上で明示的に 「Submit for Review」 を押す必要があります。ここを押すまでは審査キューに入りません

7.1 提出前チェックリスト

「Submit for Review」を押す前に、以下がすべて揃っているか確認してください。1つでも欠けていると提出ボタン自体が押せないか、提出できても高確率でリジェクトされます。

  • ビルドの処理が完了し、「有効」状態になっている(7章冒頭)
  • App情報・価格および配信状況が設定済み(5.4)
  • App Privacyのアンケートに回答済み(5.5)。未回答だと提出ボタンがブロックされます
  • 年齢制限の質問に回答済み(5.6)
  • サブスクリプションを提供する場合、Subscription Groupと商品が作成済みで、初回提出時はレビュー用スクリーンショットも添付済み(5.7)
  • 必要なサイズのスクリーンショット・説明文・サポートURL・プライバシーポリシーURLが揃っている(5.8)
  • サインインが必須なアプリの場合、App Review Informationにデモアカウントを入力済み(5.9)。これを忘れると審査担当者がサインイン画面の先に進めず即リジェクトされる、最も多い差し戻し理由の一つ
  • ペイウォールに、期間・価格・自動更新の旨・利用規約/プライバシーポリシーへのリンク・「購入を復元」ボタンが表示されている(4.6)

7.2 リジェクトされた場合

審査担当者からのフィードバックは「Resolution Center」に届きます。多くの場合、具体的にどのガイドライン番号(例: 3.1.2、5.1.1(v))に抵触したかが示されるので、該当箇所を修正して再提出します。ビルド自体に変更が不要な指摘(メタデータの文言修正のみ等)であれば、新しいビルドをアップロードし直さずにメタデータの再提出だけで審査が再開されることもあります。逆にコード修正が必要な指摘であれば、修正後に新しいビルド番号でArchiveからやり直します(6章)。


8. よくあるワナ(FAQ)

実際に開発を進める中で遭遇した(または一般的に知られている)つまずきポイントを、Q&A形式でまとめます。

Q. アップロード時に「iPadマルチタスキング対応の方向が足りない」というエラーが出た

Invalid bundle. The ‘UIInterfaceOrientationPortrait,…’ orientations were provided … but you need to include all of the ‘UIInterfaceOrientationPortrait, UIInterfaceOrientationPortraitUpsideDown, UIInterfaceOrientationLandscapeLeft, UIInterfaceOrientationLandscapeRight’ orientations …

原因: TARGETED_DEVICE_FAMILY にiPad(値 2)を含むuniversalアプリは、Info.plistの UISupportedInterfaceOrientations に4方向すべて(Portrait / PortraitUpsideDown / LandscapeLeft / LandscapeRight)を含めないと、iPadのSplit View/Slide Over対応の要件を満たせずアップロードが拒否されます。iPhone専用のつもりで作っていても、universal設定になっていると発生します。

対処: UISupportedInterfaceOrientations 配列に不足している方向(大抵は UIInterfaceOrientationPortraitUpsideDown)を追加します。Info.plistを直したら再度Archiveからやり直す必要があります(失敗したIPAをそのまま再アップロードすることはできません)。

Q. 「Upload Symbols Failed」というdSYM関連の警告が出た。アップロードは止めるべき?

止める必要はありません。サードパーティのバイナリXCFramework(Firebase・gRPC・abseil・openssl等、SPM経由で導入したもの)で頻出する警告です。これは警告であり、アップロードをブロックするエラーではありません。当該フレームワークの提供元がdSYMを同梱していない場合に出るもので、クラッシュログのシンボリケーションがそのフレームワーク内の関数に限り読みにくくなる程度の影響です。自前のコードのシンボリケーションには影響しないため、基本的に無視して進めて問題ありません。

Q. 同じバージョンでビルド番号を使い回してもいい?

同一の MARKETING_VERSION(例: 1.0.0)内では、一度アップロードに成功したビルド番号は再利用できません。ビルドごとに CURRENT_PROJECT_VERSION をインクリメントする運用にしておくと事故が少ないです。なお、処理が完了する前に拒否された(validation自体で弾かれた)ビルドが同じ番号を再利用できるかどうかはケースによって挙動が変わりうるため、迷ったら素直にインクリメントするのが安全です。

Q. シミュレータで経路検索・地図検索(MapKit)が常に空の結果を返す

MKDirectionsMKLocalSearchなどのMapKit機能がシミュレータ上で常に空の結果しか返さない場合、システムログに [com.apple.GeoServices:ResourceManifest] Failed to locate resource named "default.csv" のようなメッセージが出ていないか確認してください(xcrun simctl spawn <udid> log show --predicate 'eventMessage contains "GeoServices"' で確認できます)。出ている場合は自分のコードの不具合ではなく、そのMac上の全シミュレータ(ランタイム単位)に共通する既知の環境不良です。別のシミュレータ機種に切り替えても直りません。この場合は実機でのみ正しく検証できると割り切ってください。

Q. Sign in with Appleで、2回目のサインインでは氏名・メールアドレスが取れない

不具合ではなく仕様です。Appleは氏名・メールアドレスを初回の認可時にしか返しません。これらの情報が必要な場合は、初回サインイン時に必ず自分のバックエンド(Firestore等)に保存しておく必要があります。保存を忘れると、2回目以降どれだけ再認証しても二度と取得できません。

Q. Firebase Authでアカウント削除しようとすると requires-recent-login のようなエラーになる

Firebaseは、アカウント削除のような破壊的操作の実行に「直近の再認証」を要求する仕様になっています。しばらく前にサインインしたセッションのままいきなり削除しようとすると発生します。削除処理の直前に該当プロバイダでの再サインインを一度挟んでから削除APIを呼ぶ実装にしてください。

Q. プロジェクトをGoogle Drive等のクラウド同期フォルダに置いていて、ある日突然コード署名エラーが出るようになった

6.2節を参照してください。ビルド成果物の出力先(SYMROOT)をクラウド同期フォルダ配下に向けていないか確認し、Xcode既定のDerivedDataベースのビルドに戻してください。特にWidget拡張など「埋め込みバイナリを含むターゲット」を新たに追加した直後に発生しやすい傾向があります。

Q. サブスク機能をアプリに入れたら、審査でアカウント削除機能が無いと指摘された

アプリ内でのアカウント登録・サインインを何らかの形で必須または前提にする場合、App Store審査ガイドライン5.1.1(v)により、アプリ内から完結する形でアカウントを削除できる導線が実質的に必須です。「Webサイトでお問い合わせください」のような外部誘導のみでは不十分と判断されるケースがあります。サインイン機能を作る計画の時点で、削除機能もセットで実装スケジュールに入れておくと手戻りがありません。

Q. Googleでログインしているのに、Appleでのログインは提供していない。審査で問題になる?

なりません。Appleのガイドラインは非対称です。「Apple以外のサードパーティ製サインイン(Google等)を提供する場合はAppleサインインも同等に提供する」というルールであり、逆(Appleのみ提供)は制約されません。ただし「Googleログインだけ実装してAppleを後回しにする」という順番は審査時に指摘対象になるので、複数プロバイダを提供する予定があるなら、Appleサインインを後回しにしないことを覚えておいてください。

Q. バックグラウンドで音声ガイドを流す機能を作ったが、アプリをバックグラウンドに送ると音が止まる

Info.plistUIBackgroundModes に必要な値が入っているか確認してください。位置情報を使い続ける場合は location、音声を鳴らし続ける場合は audio が必要です。片方だけ入れていて、実際には両方の挙動が必要な機能(例: バックグラウンドでの音声ナビゲーション)だと、位置情報だけは更新されるのに音声だけが止まる、という中途半端な挙動になりがちです。

Q. RevenueCatの課金状態と、Firebase App Checkは何が違う?両方要る?

役割が全く異なります。App Checkは「このリクエストは本物の自分のアプリの、改造されていないインスタンスから来ているか」を検証するもので、課金状態は一切見ません。RevenueCatのエンタイトルメントは「このユーザーは対価を払って権利を持っているか」を検証するもので、アプリの真正性は見ません。サーバー経由のAPI呼び出しを課金者限定にしたい場合、両方を組み合わせて初めて「正規のアプリから」かつ「課金済みユーザーから」のリクエストだけを通せます。どちらか一方だけでは不十分です。

Q. Xcodeでのビルドは通るのに、機能によっては実機でしか正しく動作確認できないものがある?

あります。本ガイドで触れたものだと、MapKitの経路検索・POI検索(シミュレータのGeoServices不具合)や、Sign in with Apple / Google / Microsoftの実際の認証フロー(自動テストでの完全な自動化が難しい)が代表例です。CI/自動テストでカバーしきれない部分は、リリース前に一度は実機での手動確認を挟む前提でスケジュールを組んでおくと安心です。

Q. サインインが必須なアプリを提出したら、審査が進まないままリジェクトされた

App Review Information(5.9節)にデモアカウントを入力し忘れている可能性が非常に高いです。審査担当者は実際の人間の審査員がアプリを操作しますが、当然ながらApple/Google/Microsoftいずれのアカウントも持っていません。ログイン画面から一歩も先に進めない状態のままでは、機能を評価しようがないためリジェクトされます。デモ用のテストアカウント(ユーザー名・パスワード)を必ず用意し、App Review Informationのメモ欄に「◯◯でサインインしてください」と明記してください。

Q. ペイウォールの審査で「Guideline 3.1.2」を理由にリジェクトされた

購入画面に必須の表示要素(サブスクリプションの期間・価格・自動更新される旨・利用規約(EULA)へのリンク・プライバシーポリシーへのリンク・「購入を復元」ボタン)のいずれかが欠けている可能性が高いです(4.6節)。RevenueCatのPaywall Builderの既製テンプレートを使っていれば通常は含まれていますが、テンプレートをカスタマイズして文言や要素を削ってしまった場合に起きがちです。特に「購入を復元」ボタンは、見た目上は無くても購入自体はできてしまうため見落とされやすい項目です。

Q. サブスクリプション商品を作ったのに、初回の審査提出でエラーになる

Subscription Group内の各商品にレビュー用スクリーンショット(アプリ内でその商品が実際に表示されている画面のキャプチャ)を添付し忘れていないか確認してください(5.7節)。初回のサブスクリプション審査ではこの画像が必須で、未添付だと審査担当者がその商品をレビューできず提出がブロックされます。2回目以降の同一商品の更新では不要になりますが、新しい商品を追加するたびに再度必要になります。

Q. App Privacyのアンケートはどこまで正直に答える必要がある?「データを収集していません」にしてはダメ?

実際に収集していないなら問題ありませんが、Firebase Auth(サインイン用のメールアドレス・ユーザーID)やFirebase Analytics、RevenueCat(購入履歴・匿名ID)など、使っているSDKが裏側で何らかのデータを収集していることは珍しくありません。実態と申告内容が食い違っていることが後から判明すると、審査差し戻しだけでなく、公開後でもアプリ削除の対象になり得ます。各SDK提供元が公開しているプライバシー影響の情報(Firebase・RevenueCat等は自社サイトで一覧を公開しています)を確認してから回答するのが安全です。


以上が、Firebaseを使ったバックエンド機能・複数プロバイダサインイン・RevenueCatによるサブスク課金を組み込んだiOSアプリを、実際にApp Store Connectへ提出するまでの一連の流れです。次にアプリを作るときのチェックリストとして使ってください。

上部へスクロール