iOS アプリを初めて App Store に出すとき、一番時間を溶かすのはコードでもアセットでもありません。「Apple に何をどう説明するか」を書く作業でした。ビルドが通ってTestFlightでも動いているのに、提出後で何度か止まります。
自社の PollenShield は自宅と出発時刻を学習し家を出る5分前に1通知だけ鳴らすアプリです。構造上**authorizedAlways の位置情報**を要求します。申請都合で何度かビルドを差し替えました(配信開始は2026-04-19のニュースの通り)。
閾値設計の話は前の記事で書いたので、今回は「Xcode の外側」で詰まった点に絞ります。
この記事で扱うこと
UIBackgroundModesの宣言をうっかり広く取りすぎてリジェクトされた話Always権限のNSLocation*UsageDescription文言をユーザーと審査官の両方に向けて書き分ける- App Privacy(プライバシーラベル)を「端末内完結」で申告する時に書きづらかった項目
- Review Notes をリポジトリで管理して説明を再利用する
- サポート URL・プライバシーポリシー・スクリーンショットの「最低限ここまで」
- TestFlight ドッグフードが審査用デモ動画の素材にもなる
書いていないのは、IAP・Sign in with Apple・年齢制限まわり。PollenShieldは無料・課金なし・ログインなしで、これらの審査論点は対象外です。
全体像です。Xcodeの外側で書く文書はだいたい次の5ブロックに分かれます。
| # | ブロック | 提出前に揃えておくこと |
|---|---|---|
| 1 | Info.plist | UIBackgroundModes は使うAPIぶんだけ宣言/Always説明文に理由を1行 |
| 2 | App Privacy ★ | 端末内完結なら Data Not Collected |
| 3 | プライバシーポリシー | iOSアプリ章を追加/Info.plistと同じ言葉で書く |
| 4 | Review Notes ★ | リポジトリで版管理 |
| 5 | 提出物アセット | サポートURL/スクショ3サイズ/デモ動画素材 |
★印が後ろ倒しにすると一番痛い領域でした。
「位置情報のバックグラウンドモード」をうっかり広く取って、Guideline 2.5.4 で止まる
最初のリジェクトはここでした。UIBackgroundModes に location を入れて提出したら Guideline 2.5.4 で指摘されました。「persistent location 機能が見当たらないのに宣言している」という指摘です。
PollenShield が使う位置APIは次の3つです。
- Significant Location Change(SLC)
- Visit Monitoring(
startMonitoringVisits) - Region Monitoring(
CLCircularRegion)
このどれもOS側がイベントを発火してアプリを起こすタイプのAPIで、こちらが連続的にGPSを回しているわけではない。背景モード一覧に location を入れていたので「機能と宣言が合っていない」と見られました。
修正は UIBackgroundModes から location を一行落とすだけ。
UIBackgroundModes:
- fetch
- processing
fetch と processing は必要なので残します。位置情報まわりは「OS 主導でアプリが起こされる」設計なので背景モード宣言なしで動きます。最初から正確に取れていたらリジェクト1回ぶんの往復を踏まずに済んだ、というのが教訓です。
地味なポイントとして、この修正でも CFBundleVersion(ビルド番号)は必ず上げないと再アップロードできません。CURRENT_PROJECT_VERSION: "2" のようにビルド番号だけ進める運用です。
Always 権限の説明文は「ユーザーへの言葉」と「審査官への言葉」の両方を兼ねる
authorizedAlways を要求するアプリで一番気を使うのが、Info.plist の説明文 2 種類です。
NSLocationWhenInUseUsageDescriptionNSLocationAlwaysAndWhenInUseUsageDescription
ここに書いた文章は起動時のダイアログにそのまま出ます。審査官は「なぜ Always が必要か」をこの文言で読みます。最終的に次の2行に落ち着きました。
NSLocationWhenInUseUsageDescription:
"出発時刻を学習して、花粉が多い日の朝に通知するために位置情報を使用します。"
NSLocationAlwaysAndWhenInUseUsageDescription:
"出発時刻を学習して、家を出る直前に花粉通知を届けるために位置情報を使用します。位置情報は端末内でのみ処理され、外部に送信されません。"
Always のほうにだけ「端末内でのみ処理され、外部に送信されません」を入れているのが意図的なポイントです。ここで疑念を1文で潰しておくと、後段のプライバシーラベル申告とも整合が取れます。
逆にやってはいけないのが、「より良い体験のために」みたいなふわっとした言葉だけで Always を要求するパターンです。PollenShield の場合は、「夜間の自宅滞在を学習する」「家を出た瞬間を検知する」という必然性を文言で見える化しました。
App Privacy は「端末内完結」を素直に書ける構造を、コードと文言の両側で揃えておく
「App Privacy」セクションは、データタイプごとに「収集するか / 紐付けるか / トラッキングに使うか」を申告する仕組みです。選択を間違えると後で全部やり直しです。
PollenShield は「端末内完結・外部送信なし・トラッキングなし」で運用しています。Locationカテゴリの申告はData Not Collectedに倒しました。具体的には、
- 学習結果(自宅座標・出発時刻)は
UserDefaultsに保存する - API リクエストには 緯度経度を 1km グリッド単位に丸めた値しか乗せない(個人を特定する精度では送らない)
- 端末 UUID 等の識別子を発行・送信しない
これを最初に決めておかないと、Privacy申告のチェックボックスとコードのふるまいが噛み合わなくなります。
合わせてプライバシーポリシーにも iOS アプリの章を増やしました。playparkの場合は
- 位置情報の利用目的(出発時刻学習・花粉通知)
- 送信先(自社の API のみ、緯度経度は 1km グリッドに丸め)
- 保持期間(端末側のみ、サーバ側は短期キャッシュ)
- トラッキングを行わない旨
を4段で書いています。Info.plistの説明文と表現を揃えておくと疑念が湧きづらくなります。
3箇所のうち1箇所でも違う言い方だと、審査側は必ず突きます。「同じ語彙で3回書く」を意識するだけで指摘は減ります。
アップデートのたびに Review Notes を書き直さないために、リポジトリで版管理する
PollenShield はv1.0配信後すぐにv1.1を出しました。自宅学習ロジックをSLC単独からVisit/Region Monitoring併用に差し替えました。
「内部実装が変わった」アップデートは「権限の使い方が変わったかも」と疑われます。何も書かずに出すと止まることがあります。バージョンごとにReview Notes欄のテキストをリポジトリで管理しています。
本バージョンは位置情報ロジックの精度改善を含みます。
変更点:
- 自宅学習のアルゴリズム刷新(Significant Location Change のみの入力から、
Visit Monitoring / Region Monitoring を併用する構成へ変更)
- 出発時刻検知の精度向上
- horizontalAccuracy による低精度サンプルのフィルタ追加
用途・ユーザー影響に変更はありません:
- 権限レベル: Always(変更なし)
- 利用目的: 自宅学習に基づく花粉予報通知(変更なし)
- データ取扱い: 端末内完結・外部送信なし(変更なし)
- 新規収集データ項目: なし
Info.plist の各 UsageDescription の文言は前バージョンから変更していません。
App Privacy の申告内容にも変更はありません。
ポイントは「変更したこと」と「変更していないこと」を同じ密度で書くことでした。「権限と申告は変わってない」を端折りがちになるので意識的に配分します。
このノートを docs/ に置いておくと、「前回どう書いたっけ」を検索する作業が消え、社内レビューにも使えます。
サポート URL とスクリーンショットは、提出当日の朝に用意するものではない
軽く見られがちなのがSupport URLとスクリーンショットです。「無いと提出できない」枠なので、後回しにすると当日の朝に慌てます。
LP のリポジトリ側で /support ページを1枚増やし、基本的な使い方・よくある質問・連絡先メールアドレスをまとめています。
スクリーンショットも最低限「アプリが何の役に立つかを5秒で理解できる絵」が必要です。PollenShieldは「家を出る直前に1通知が出るだけ」で能動的に開く画面が少ないので、
- ホーム画面の通知バナー風のモック
- アプリ起動時の今日の花粉量画面
- 設定画面が無い(=何も無い)こと自体を伝える画面
の3枚で構成しました。「機能が薄いアプリ」の場合、全機能を出し切るのは戦略的に正解で、「隠し機能はありません」を見せると確認質問が来づらくなります。
要求される画像サイズはiPhone 6.7/6.5/5.5インチの3種類が事実上必須です。デザインデータは6.7インチ基準で作り、1.0倍 / 0.75 倍 / 0.6 倍で書き出しています。
位置情報アプリの審査用デモ動画は、TestFlight ドッグフードの副産物として作る
バックグラウンドで自宅学習する系のアプリは、審査側から「動作の様子を見せてほしい」と来る可能性があります。Region Monitoringは実機+実生活でしか確認できません。
ここで効くのが、TestFlight で配布したビルドを1〜2週間持ち歩いて学習させる期間を最初から確保しておくことです。PollenShield の場合、
- 自分の iPhone で 7 日以上の通勤往復をログ
- DEBUG ビルドで「学習状態デバッグ」画面のスクリーンショットを毎日撮る
- 自宅確定の瞬間・引っ越し検知の挙動をデバッグ画面の動画で押さえる
を並行してやっています。「動画を共有してほしい」と来た時に当日提出可能な素材が手元にある状態になり、後回しにすると1〜2週間止まる可能性があります。
デバッグ画面はDEBUGビルド限定なのでReleaseバイナリには入らず、「審査官にだけ見せる」設計です。
Export Compliance とテストアカウント — 最後の小ネタ
Export Compliance: 独自暗号化が無ければ ITSAppUsesNonExemptEncryption: false を書きます。暗号質問をスキップできます。忘れると毎回手で埋めます。
テストアカウント: PollenShieldは不要でしたが、ログインありのアプリでは「テストアカウント情報」をReview Notesに書く欄があります。忘れると即返却です。
まとめ — Apple に対する「文書としての説明責任」を、コードと同じ温度で書く
出してみて一番強く感じたのは、App Store 申請のリジェクト要因の半分以上が、コードではなく文書側にあるということでした。
UIBackgroundModesは機能と宣言を一致させるNSLocation*UsageDescriptionはユーザー向けと審査官向けを1行で兼ねる- App Privacyの申告は、コード・Info.plist・プライバシーポリシーの3箇所で揃える
- Review Notes はリポジトリで版管理する
- サポートURL・スクショ・テストアカウントは提出前日に思い出すと泣く
- 審査用動画素材はTestFlightドッグフード期間に毎日撮る
コードを書く時間と同じくらい、申請文書を書く時間を最初から見積もっておく、というのが学びでした。
- プロダクト LP: pollen-shield.vercel.app
- App Store 配信: PollenShield
「設定は、ゼロ」の実物は App Store からどうぞ。



