App Store Connect の「Activity」で TestFlight のプロセッシングが終わって安心していたら、数分後に Apple から ITMS-90683: Missing Purpose String in Info.plist という件名のメールが届く。Rork で初めて iOS の submit まで通した人の多くが、ここで一度足を止めることになります。私自身、2014 年から個人で iOS アプリを公開し続けて累計 5,000 万 DL に達した今でも、新しい SDK を入れたタイミングで同じメールを年に数回もらいます。
このエラーは「ビルド失敗」ではないので、ローカルでは気づきにくいのが厄介です。バイナリは無事に App Store Connect に届いていて、内部処理の中で Apple が Info.plist を静的解析した結果として返ってくる、いわば「審査前の自動 reject」に近い扱いになります。Rork で生成したプロジェクト構成を前提に、原因の整理から app.json 経由での修正、そして再 submit までの最短ルートを順番に追っていきます。
ITMS-90683 が出る本当の理由
Apple がこのエラーを返すのは、アプリのバイナリの中で「特定の API を呼ぶコード」が検出されたのに、対応する説明文(Purpose String)が Info.plist に書かれていなかった場合です。Apple のドキュメントでは「Privacy-Sensitive Data API」と呼ばれていて、たとえば次のような領域を触ると説明文が必須になります。
- 写真ライブラリ・カメラ・マイク
- 位置情報・モーション・健康データ
- 連絡先・カレンダー・リマインダー
- Bluetooth・ローカルネットワーク・FaceID
- トラッキング(IDFA)・音声認識・Siri
ポイントは、自分が直接 API を呼んでいなくても、依存している SDK が内部で呼び出していれば検出されるという点です。Rork で npx expo install 経由で入れた React Native ライブラリの中には、内部的にカメラやマイクへアクセスする実装を持つものが少なくありません。バナー広告 SDK が IDFA を参照するケースもよくあります。
つまりこのエラーは「あなたのコードが直接触っていないから関係ない」では済まないのです。
まず確認するべきはエラーメールの中身
Apple から届くメールの本文には、欠けている key の名前が必ず列挙されています。たとえばこんな形です。
The app's Info.plist must contain an NSCameraUsageDescription key
with a string value explaining to the user how the app uses this data.
NSCameraUsageDescription の部分が、追加する key 名そのものです。複数列挙されていることもあるので、まずは全部書き写してから次のステップに進んでください。
主な key と、それが要求される代表的なシナリオを表の代わりにリスト化しておきます。
NSCameraUsageDescription— 写真撮影、QR コード読み取り、ライブ配信系 SDKNSPhotoLibraryUsageDescription— 写真ライブラリからの選択NSPhotoLibraryAddUsageDescription— 撮影画像をライブラリに保存NSMicrophoneUsageDescription— 音声録音、ビデオ撮影、音声 SDKNSLocationWhenInUseUsageDescription— 位置情報(前景)NSLocationAlwaysAndWhenInUseUsageDescription— 位置情報(背景含む)NSBluetoothAlwaysUsageDescription— BLE 機器との通信NSLocalNetworkUsageDescription— Bonjour 経由のローカル LAN 通信、Chromecast 系NSFaceIDUsageDescription— FaceID による認証NSUserTrackingUsageDescription— IDFA 取得(AdMob、Firebase Analytics などで要求されがち)NSContactsUsageDescription— 連絡先の参照
Rork プロジェクトでの正しい書き方
ここで一番大事なのが、Info.plist を直接編集してはいけないという点です。
Rork は内部で Expo のビルドシステム(EAS Build)を使っており、ビルドのたびに app.json(または app.config.js)から Info.plist を再生成します。Xcode で開いて手動で書き換えても、次のビルドで上書きされて元に戻ります。私もこれで一度 reject を 3 回連続で食らったことがあり、原因が「直すべきファイルが違った」だけだったと気付くまで半日溶かしました。
正しいのは、app.json の expo.ios.infoPlist セクションに追記する方法です。AdMob と写真選択機能、それから FaceID ログインを実装したアプリを例にすると、次のようになります。
{
"expo": {
"name": "MyRorkApp",
"ios": {
"bundleIdentifier": "com.example.myrorkapp",
"infoPlist": {
"NSCameraUsageDescription": "QRコードの読み取り時にカメラへのアクセスを使用します。",
"NSPhotoLibraryUsageDescription": "プロフィール画像を選択するために写真ライブラリへアクセスします。",
"NSPhotoLibraryAddUsageDescription": "生成した画像を写真ライブラリに保存します。",
"NSFaceIDUsageDescription": "Face IDで安全にログインするために使用します。",
"NSUserTrackingUsageDescription": "あなたに合った広告を表示するために、他社アプリやサイトでの行動を関連付けます。"
}
}
}
}書き込んだら、次の EAS Build から自動で Info.plist に反映されます。Xcode を開いて確認する必要はありませんが、念のため ios/MyRorkApp/Info.plist を grep してキー名が入っているかチェックすると安心です。
文言の品質が後の審査結果に効く
ITMS-90683 をクリアするだけなら、極論「test」と書いても通ります。しかし、ここで適当な文言を入れると、TestFlight は越えられても本審査(App Review)で別途リジェクトされることがあります。私が過去に食らったリジェクト理由には次のようなものがありました。
- 「カメラへのアクセスをこのアプリで使用」と書いただけで、何のためかが明示されていない
- 英語版のみで日本語ローカライズが用意されていない
- 説明文と実際の機能が食い違っている(例: 写真選択しかしないのに「撮影に使用」と書いていた)
文言を考えるときの目安は「ユーザーがアラートを見た瞬間、何のためにこの許可を求められているか即座に理解できるか」です。具体的なユースケースを 1 文で書き、丁寧語で揃えるのが無難です。
ローカライズが必要な場合は、expo-localization と InfoPlist.strings を組み合わせるか、Expo SDK 50 以降であれば app.json 内で expo.locales を指定して言語ごとの文字列を用意できます。
{
"expo": {
"locales": {
"ja": "./languages/ja.json",
"en": "./languages/en.json"
}
}
}そして languages/ja.json を次のように作っておきます。
{
"NSCameraUsageDescription": "QRコードの読み取り時にカメラへのアクセスを使用します。",
"NSPhotoLibraryUsageDescription": "プロフィール画像を選択するために写真ライブラリへアクセスします。"
}英語版も同じキーで en.json を用意すれば、地域に応じて適切な文言が表示されます。複数言語でリリースする予定があるなら、最初からこの形にしておくと後で楽です。
SDK が暗黙的に要求するケースの見つけ方
問題の半分は「自分が呼んでいない API を SDK が呼んでいる」ことに起因します。エラーメールに NSUserTrackingUsageDescription が含まれているのに自分のコードに requestTrackingAuthorization が一切ないなら、まず疑うのは広告 SDK と分析 SDK です。
私の手元で実際に発生したケースをいくつか挙げます。
react-native-google-mobile-ads(AdMob) →NSUserTrackingUsageDescriptionを要求expo-image-picker→NSPhotoLibraryUsageDescriptionとNSCameraUsageDescriptionexpo-camera→NSCameraUsageDescriptionとNSMicrophoneUsageDescription(ビデオ撮影機能を持つため)expo-local-authentication→NSFaceIDUsageDescriptionreact-native-zeroconf→NSLocalNetworkUsageDescriptionとNSBonjourServices@react-native-firebase/analytics→NSUserTrackingUsageDescription(IDFA を取る設定のとき)
どの SDK が何を要求するかは公式ドキュメントで明記されていることもありますが、書かれていないこともあります。一番確実なのは、エラーで指摘された key をそのまま app.json に追加することです。使っていない機能でも、SDK が内部で API を参照している以上、説明文を書く義務が発生します。
修正後の再 submit までの流れ
app.json を更新したら、次の手順で再 submit します。
# 1. ビルドバージョンを上げる(必須)
# app.json の expo.ios.buildNumber をインクリメント
# 例: "1.0.3" → "1.0.4"
# 2. EAS Build で再ビルド
eas build --platform ios --profile production
# 3. ビルド完了後、submit
eas submit --platform ios --latestここでよくあるミスが、buildNumber を上げ忘れて submit して ERROR ITMS-90189: Redundant Binary Upload を食らうパターンです。同じビルド番号は受け付けてもらえません。Rork 経由でビルドしている場合も、生成後に app.json を直接編集してから再ビルドする手順を踏むのが安全です。
submit してから 5〜15 分後に App Store Connect の Activity を確認し、新しいビルドが「Ready to Test」になっていれば成功です。同じ ITMS-90683 が再び返ってきた場合は、メール本文に列挙されている key を全て infoPlist に書ききれているか、もう一度照合してみてください。
予防として組んでおきたいチェックリスト
リジェクトを毎回 submit してから知る、というのは正直しんどいので、私は新しい SDK を入れたタイミングで次の手順を回すようにしています。
npx expo installの直後に、SDK の README で iOS 用 Info.plist 要件を確認する- 要件があれば即
app.jsonのinfoPlistに追記する(後回しにすると忘れる) - ローカルで
npx expo prebuild --platform ios --cleanを実行し、生成されたios/*/Info.plistを grep で確認する - submit 前に
eas buildの出力ログからBundle was successfully builtを確認する
expo prebuild を一度ローカルで回しておくと、submit 前に Info.plist の中身を目で確認できるのが大きいです。Rork のクラウドビルドだけに依存していると、この中間ファイルを見るタイミングがなくなり、reject されてから気付くサイクルになりがちです。
一歩深いところ — Privacy Manifest との関係
iOS 17 から導入された Privacy Manifest(PrivacyInfo.xcprivacy)は、Info.plist の Purpose String とは別のレイヤーで「データ収集の目的」を申告する仕組みです。ITMS-90683 をクリアしても、Privacy Manifest が不十分だと別の警告(ITMS-91053 など)が返ってきます。
両者の関係を端的に整理すると、Info.plist の Purpose String はユーザーへのアラートに表示される文言、Privacy Manifest は Apple への申告と App Store のプライバシーラベル生成に使われる構造化データです。Rork で React Native の主要 SDK を入れているなら、SDK 側が PrivacyInfo.xcprivacy を同梱しているか確認しておくと、後々の追加リジェクトを避けられます。
ITMS-90683 に出くわすたびに私が思うのは、これは「コードが間違っている」のではなく「ユーザーへの説明が足りていない」というメッセージだということです。短い説明文を 5〜10 個書くだけで、ユーザーがインストール時に得る安心感はかなり変わります。リジェクトメールが届いたら、一手間かけて文言を考える機会だと捉えるのが結果的にいちばん近道です。
同じ場面でつまずいている方の参考になれば幸いです。お読みいただきありがとうございました。