私自身、AdMobで広告収入を得る個人アプリを2014年から運営してきたのですが、Rorkで新しい広告付きアプリを作って実機で試した時、ATT(App Tracking Transparency)のダイアログが一度も出てこないことに気づきました。AdMobのコードは正しく入っているし、Info.plistにも見覚えのある設定を書いたつもりでしたが、何度起動してもあのおなじみの「Allow "アプリ名" to track your activity across other companies' apps and websites?」が表示されません。
コンソールにはエラーが出ない、ビルドも通る、AdMobの広告は表示される。それなのにIDFA(広告識別子)はゼロのまま、Firebase Analyticsの収益データも空欄のまま。「AppTrackingTransparency framework が動いていない」というのは、Rorkで生成されたコードでは意外なほど頻繁に起きる、見つけにくい不具合です。
ここでは私が実際にハマった3つの原因と、それぞれの修正手順を整理します。AdMobやFirebaseでマネタイズする方には、特に直結する内容かと思います。
原因①: Info.plist の NSUserTrackingUsageDescription が空・未設定
最も多いのがこのパターンです。Rorkは標準でATT用の説明文を app.json に書いてくれない場合があり、ビルド時に Info.plist が生成されても NSUserTrackingUsageDescription キー自体が抜け落ちることがあります。
iOS 14.5以降、このキーが Info.plist に存在しないアプリで requestTrackingAuthorization() を呼んでも、iOSはダイアログを表示せず、即座に .denied を返します。エラーは投げられないため、開発者は「呼び出しは成功した」と勘違いしやすい厄介な仕様です。
// app.json — ios.infoPlist セクションに必ず追加する
{
"expo": {
"ios": {
"infoPlist": {
"NSUserTrackingUsageDescription": "あなたに合った広告を表示するため、この識別子の使用許可をお願いします。"
}
}
}
}説明文には実際の利用目的を、ユーザー目線で具体的に書きます。Appleの審査では「広告のため」とだけ書くと拒否されることもあるため、「あなたに合った広告」「より関連性の高いコンテンツのため」のような表現が安全です。
修正後は eas build または expo prebuild --clean で Info.plist を再生成します。私はこの「prebuild --clean」を忘れて2時間溶かしました。生成済みの ios/ フォルダがあるとそちらが優先され、app.json の変更が反映されないのです。
原因②: requestTrackingAuthorization() の呼び出しタイミングが早すぎる
ATTのダイアログは、アプリが完全にフォアグラウンドに来てからでないと表示されません。useEffect の中で何も考えずに呼び出すと、起動直後でアプリが「アクティブ」状態になる前に呼ばれてしまい、ダイアログが出ずに .notDetermined が返るだけで終わります。
これも本当に分かりにくいのですが、シミュレータでは正常に見える挙動が、実機では沈黙する形で失敗することが多いです。私の場合は、TestFlightでベータユーザーから「広告のダイアログが出ない」とフィードバックをもらって初めて気づきました。
// ✅ 正しい呼び出し方
import { useEffect, useRef } from "react";
import { AppState, Platform } from "react-native";
import {
requestTrackingPermissionsAsync,
getTrackingPermissionsAsync,
} from "expo-tracking-transparency";
export default function App() {
const requestedRef = useRef(false);
useEffect(() => {
if (Platform.OS !== "ios") return;
const subscription = AppState.addEventListener("change", async (state) => {
if (state !== "active" || requestedRef.current) return;
requestedRef.current = true;
const current = await getTrackingPermissionsAsync();
if (current.status === "undetermined") {
const result = await requestTrackingPermissionsAsync();
console.log("ATT result:", result.status);
// 期待される出力: "granted" / "denied"
}
});
return () => subscription.remove();
}, []);
return /* ... */;
}ポイントは3つあります。AppState で active を待つこと、一度だけ呼び出すこと(requestedRef)、そして既に決定済みなら再度呼ばないことです。Rorkに「ATTを実装して」と頼むと、たまに useEffect の中で直接呼び出すコードが生成されるので、上記のパターンに置き換えると安定します。
原因③: AdMob SDK の初期化順序が逆になっている
これがおそらく一番ハマりやすい落とし穴です。AdMob(Google Mobile Ads)は、SDKの初期化時にIDFAを取得しようとするため、ATTの結果が確定する前に初期化されると、その後に許可しても広告のパーソナライズが効かないケースがあります。
正しい順序は次の通りです。
- アプリ起動
- ATT ダイアログを表示し、ユーザーの選択を待つ
- 結果を確認した後で AdMob SDK を初期化する
import mobileAds from "react-native-google-mobile-ads";
import { requestTrackingPermissionsAsync } from "expo-tracking-transparency";
async function initAdsWithConsent() {
if (Platform.OS === "ios") {
const { status } = await requestTrackingPermissionsAsync();
console.log("ATT decided:", status);
}
// ATT の結果が確定してから初期化することで、
// パーソナライズ広告が正しく機能する
await mobileAds().initialize();
console.log("AdMob initialized");
}Rorkに広告SDKの追加を依頼すると、しばしば mobileAds().initialize() を App.tsx の最上部に置くコードが生成されます。これだとATTの結果を待たずに初期化されてしまうため、自分で順序を入れ替える必要があります。
テスト時の落とし穴: 一度決定すると再表示できない
ATTのダイアログは、ユーザーが一度「許可」または「拒否」を選ぶと、そのアプリでは二度と自動表示されません。開発中にこの仕様を忘れると、「修正したのにダイアログが出ない=直っていない」と勘違いします。
実機でリセットする手順は次の通りです。
- 同じアプリで再テストしたい場合: 設定アプリ → プライバシーとセキュリティ → トラッキング → 該当アプリのトグルをオフ・オン
- 完全にリセットしたい場合: 該当アプリをアンインストールして再インストール(ただしIDFAは保持されるため、完全な初期状態にはならない)
- シミュレータの場合: メニューから Device → Erase All Content and Settings
私は最初これを知らず、コードを直すたびに「直ってない…」と1日ハマりました。実機の設定アプリから対象アプリをタップして、許可状態を直接確認するのが確実です。
デバッグ用: 現在の許可状態をログに出す
トラブルシューティング中は、起動のたびに現在のATT状態をログに残しておくと原因特定が早まります。
import { getTrackingPermissionsAsync } from "expo-tracking-transparency";
useEffect(() => {
(async () => {
const status = await getTrackingPermissionsAsync();
console.log("=== ATT status ===");
console.log("status:", status.status); // "granted" | "denied" | "undetermined"
console.log("canAskAgain:", status.canAskAgain);
console.log("expires:", status.expires);
// 期待される出力例:
// status: "undetermined" → まだ聞いていない(リクエスト可能)
// status: "denied" canAskAgain: false → ユーザーが拒否済み(再リクエスト不可)
})();
}, []);canAskAgain: false が返ってきたら、もうコード側からは表示できません。ユーザーに設定アプリでの操作をお願いするUIを用意するのが現実的な落とし所です。
私の経験からの提案
ATTの実装は、Rork の生成コードに任せきりにするのではなく、自分の手で app.json・呼び出しタイミング・SDK初期化順序の3点を必ず確認するクセをつけるのが安全です。私は今、Rorkで広告アプリを作るときには、まずこの3点だけを書いた最小サンプルを作って実機で動作確認してから、本番のロジックを足していくようにしています。
ATTの実装は地味で目立ちませんが、ここを正しく組んでおくとTik広告のCPMが体感で1.5〜2倍ほど変わってくることがあります。地道に整えるだけの価値はあります。
関連する記事として Rork でプッシュ通知が動かない時の対処法 や Rorkアプリでパーミッションが効かない時の解決ガイド も合わせて読んでいただけると、iOSのプライバシー関連の地雷を一通り回避できるかと思います。
最後までお付き合いいただき、ありがとうございました。同じ落とし穴で時間を溶かしている方の参考になれば嬉しいです。