RORK LABEN
BUILD — Rork Max はクラウド上の実機 Mac に Xcode と iOS SDK を載せ、SwiftUI を書き、ビルドし、エラーを読んで直す反復を回します。コードを吐いて終わりではない点が生成物の質に効いていますNATIVE — 生成されるのは React Native ではなく純粋な Swift / SwiftUI です。AR や Metal、ウィジェットなど React Native からは届かない機能に手が届くことが、他ビルダーとの実質的な差になりますPLATFORMS — 対応は iPhone・iPad・Apple Watch・Apple TV・Vision Pro に加えて iMessage まで含みます。ウォッチや拡張から入る設計を試すのに向いていますCOMPANION — Rork Companion により、有料の Apple Developer アカウントなしで生成したアプリを実機の iPhone で確認できます。最初の一本を試す段階の障壁が一段下がりましたPRICING — 無料で開始でき有料プランは月額25ドルから、Rork Max は月額200ドルの Max プランです。回収できる本数と規模を先に見積もっておく価値がありますDEADLINE — Google Play は2026年8月31日以降、新規アプリと更新の双方に対象 API レベル36 以上を要求します。残り10日で、生成物の targetSdkVersion は自分で確認が必要ですBUILD — Rork Max はクラウド上の実機 Mac に Xcode と iOS SDK を載せ、SwiftUI を書き、ビルドし、エラーを読んで直す反復を回します。コードを吐いて終わりではない点が生成物の質に効いていますNATIVE — 生成されるのは React Native ではなく純粋な Swift / SwiftUI です。AR や Metal、ウィジェットなど React Native からは届かない機能に手が届くことが、他ビルダーとの実質的な差になりますPLATFORMS — 対応は iPhone・iPad・Apple Watch・Apple TV・Vision Pro に加えて iMessage まで含みます。ウォッチや拡張から入る設計を試すのに向いていますCOMPANION — Rork Companion により、有料の Apple Developer アカウントなしで生成したアプリを実機の iPhone で確認できます。最初の一本を試す段階の障壁が一段下がりましたPRICING — 無料で開始でき有料プランは月額25ドルから、Rork Max は月額200ドルの Max プランです。回収できる本数と規模を先に見積もっておく価値がありますDEADLINE — Google Play は2026年8月31日以降、新規アプリと更新の双方に対象 API レベル36 以上を要求します。残り10日で、生成物の targetSdkVersion は自分で確認が必要です
記事一覧/アプリ開発
アプリ開発/2026-08-21初級

Apple の自動解析は、purpose string が書いてあるかではなく何に使うかを読んでいます

Rork や Expo で生成した iOS アプリを提出すると、Guideline 5.1.1 で「placeholder or otherwise insufficient purpose strings」と返されることがあります。キー名が示されないこの指摘の読み方と、app.json から文言を書き直すまでの手順をまとめました。

App Store88Guideline 5.1.1Info.plist6purpose stringExpo176Rork540審査10プライバシー11

壁紙アプリの更新を提出した翌朝、App Store Connect に赤いバッジが付いていました。開くと Guideline 5.1.1、本文は三行ほどです。どのキーが問題なのかも、どのファイルを直せばよいのかも書かれていません。

ビルドは通っています。TestFlight にも配れています。前回の更新は、ほぼ同じ構成で審査を通っていました。

その日の最初の一時間は、何を指摘されたのかを突き止めるためだけに消えました。同じところで止まっている方に向けて、あの朝に辿った道筋をそのまま書いておきます。

An automated analysis of the submission indicates the app includes placeholder or otherwise insufficient purpose strings

届く文面はこれです。原文のまま引用します。

An automated analysis of the submission indicates the app includes placeholder or otherwise insufficient purpose strings. Purpose strings must clearly and completely describe your use of data.

読みどころは二つあります。

ひとつは automated analysis。人間のレビュアーが画面を触った結果ではなく、提出されたバイナリの Info.plist を機械が読んだ結果です。2026年7月20日ごろから、この文面での差し戻しが目立つようになりました。

もうひとつは placeholder or otherwise insufficient説明文が「無い」とは一言も言っていません。書いてある、けれど中身が足りない、という指摘です。

ここが、提出前の検証で返ってくる ITMS-90683: Missing Purpose String in Info.plist との決定的な違いです。あちらは欠落、こちらは不足。対処もまったく別物になります。欠落の方で止まっている場合は、RorkでビルドしたiOSアプリがTestFlightで「ITMS-90683」拒否されるを先に読んでください。

そして厄介なことに、この自動解析はキー名を教えてくれません。どの NS...UsageDescription が引っかかったのか、自分で見つける必要があります。

既定の文言が入っているのは、書き忘れた結果ではありません

Rork が書き出す Expo プロジェクトでは、写真やカメラを扱うライブラリを入れた時点で、対応する説明文が自動的に追加されます。config plugin と呼ばれる仕組みが、ビルド時に Info.plist へ書き込んでいます。

たとえば expo-image-picker の既定値はこうです。

{
  "expo": {
    "plugins": [
      ["expo-image-picker", { "photosPermission": "Allow $(PRODUCT_NAME) to access your photos" }]
    ]
  }
}

photosPermission を指定しなければ、この文言がそのまま入ります。$(PRODUCT_NAME) はビルド時にアプリ名へ置き換わるので、完成したバイナリには Allow 壁紙アプリ to access your photos のような一文が残ります。

英語として読めてしまう分、たちが悪いところです。空欄ではないので、自分では書いたつもりになる。けれど自動解析から見れば、これは典型的な placeholder です。

私が最初に引っかかったのは、実はもう一段手前でした。

その壁紙アプリは、生成した画像を写真アプリへ保存するだけで、ライブラリを読み取る機能を一切持っていません。必要なのは NSPhotoLibraryAddUsageDescription(追加)だけのはずです。ところが Info.plist には、読み取り用の NSPhotoLibraryUsageDescription も並んで入っていました。ライブラリを入れた副作用です。

自動解析は「使っていない資源を宣言している」ケースも拾います。文言を丁寧に書き直しても、その資源をアプリが触っていなければ、説明として成立しないからです。書き直す前に、そもそも要るのかを決める必要がありました。

Info.plist に実際に何が入っているかを見る

app.json を眺めていても、最終的に何が書き込まれるかは分かりません。ネイティブ側を一度生成して、実物を確認します。

npx expo prebuild --platform ios
grep -A1 "UsageDescription" ios/*/Info.plist

ひとつ注意があります。Expo SDK 57 以降、expo prebuild は既定で ios/android/ を破棄して作り直します。これらのディレクトリに手作業で入れた変更があるなら、実行前に何を入れたのか控えておいてください。Rork が生成したまま触っていないプロジェクトであれば、そのまま実行して構いません。

出力はこのような形になります。

	<key>NSPhotoLibraryUsageDescription</key>
	<string>Allow Wallpaper Studio to access your photos</string>
--
	<key>NSPhotoLibraryAddUsageDescription</key>
	<string>保存した壁紙を写真アプリのアルバムに書き出すために…</string>
--
	<key>NSCameraUsageDescription</key>
	<string>Allow Wallpaper Studio to access your camera</string>

どのライブラリがどのキーを連れてくるのかは、覚えておくと当たりが早くなります。実際によく見かける組み合わせはこのあたりです。

ライブラリ入る可能性のあるキー
expo-image-pickerNSPhotoLibraryUsageDescription / NSPhotoLibraryAddUsageDescription / NSCameraUsageDescription / NSMicrophoneUsageDescription
expo-media-libraryNSPhotoLibraryUsageDescription / NSPhotoLibraryAddUsageDescription
expo-cameraNSCameraUsageDescription / NSMicrophoneUsageDescription
expo-locationNSLocationWhenInUseUsageDescription / NSLocationAlwaysAndWhenInUseUsageDescription
expo-tracking-transparencyNSUserTrackingUsageDescription
expo-local-authenticationNSFaceIDUsageDescription

写真を選ぶ機能だけを入れたつもりが、カメラとマイクの説明文まで付いてくることがあります。ライブラリ側が両方の入口を持っているためで、こちらの設定漏れではありません。それでも、使っていない資源の宣言として自動解析に拾われる点は同じです。

見た瞬間に分かるものもあれば、判断に迷うものもあります。件数が増えてくると目視では取りこぼすので、私は判定を機械に任せることにしました。

既定の文言を洗い出す

依存パッケージなしで動く短いスクリプトです。Info.plist から NS...UsageDescription を全部抜き出し、既定文言のパターンと突き合わせます。

import { readFileSync } from "node:fs";
 
// Expo の config plugin が既定で書き込む文言(そのまま残りやすいもの)
const PLUGIN_DEFAULTS = [
  /^Allow .+ to access your photos$/i,
  /^Allow .+ to access your camera$/i,
  /^Allow .+ to access your microphone$/i,
  /^Allow .+ to use your location$/i,
  /^Allow .+ to access your (contacts|calendar|reminders)$/i,
  /^Allow .+ to save photos$/i,
];
 
// 「使います」としか言っていない、理由のない一文
const NO_REASON = [
  /^(this app|the app) (needs|requires|uses) access to your [^.]+\.?$/i,
  /^(for|to) (better|improve) (user )?experience\.?$/i,
];
 
const plistPath = process.argv[2] ?? "ios/Info.plist";
const xml = readFileSync(plistPath, "utf8");
 
const pairs = [];
const re = /<key>(NS[A-Za-z]*UsageDescription)<\/key>\s*<string>([\s\S]*?)<\/string>/g;
let m;
while ((m = re.exec(xml)) !== null) {
  pairs.push({ key: m[1], value: m[2].trim() });
}
 
let flagged = 0;
for (const { key, value } of pairs) {
  const reasons = [];
  if (PLUGIN_DEFAULTS.some((r) => r.test(value))) reasons.push("plugin の既定文言のまま");
  if (NO_REASON.some((r) => r.test(value))) reasons.push("用途の説明がない");
  if (value.length < 25) reasons.push("短すぎる(25文字未満)");
 
  if (reasons.length > 0) {
    flagged++;
    console.log(`FLAG ${key}`);
    console.log(`     "${value}"`);
    console.log(`     → ${reasons.join(" / ")}`);
  } else {
    console.log(`OK   ${key}`);
  }
}
 
console.log(`\n${pairs.length} 件中 ${flagged} 件が書き直しの対象です。`);
process.exit(flagged > 0 ? 1 : 0);

node audit-purpose-strings.mjs ios/YourApp/Info.plist で走ります。手元の Info.plist に対して実行した出力がこちらです。

FLAG NSPhotoLibraryUsageDescription
     "Allow Wallpaper Studio to access your photos"
     → plugin の既定文言のまま
OK   NSPhotoLibraryAddUsageDescription
FLAG NSCameraUsageDescription
     "Allow Wallpaper Studio to access your camera"
     → plugin の既定文言のまま
FLAG NSMicrophoneUsageDescription
     "This app needs access to your microphone."
     → 用途の説明がない
FLAG NSLocationWhenInUseUsageDescription
     "Allow Wallpaper Studio to use your location"
     → plugin の既定文言のまま

6 件中 4 件が書き直しの対象です。

終了コードは、対象が残っていれば 1、ゼロなら 0 を返します。CI に組み込むなら、この値をそのまま使えます。

25文字という下限は、日本語で「何のために」まで書くと自然に超える長さから決めました。厳密な基準ではありません。引っかかった文言を人間が読み直すきっかけとして置いています。

書き直しの基準は「どの資源を・何のために・どこまで」

Apple の要求は clearly and completely describe your use of data です。この一文を三つに割ると、そのまま書き直しの型になります。

キー置き換え前置き換え後
NSPhotoLibraryAddUsageDescription Allow $(PRODUCT_NAME) to access your photos 選んだ壁紙を写真アプリのアルバムへ保存するために使用します。ライブラリ内の写真を読み取ることはありません。
NSCameraUsageDescription Allow $(PRODUCT_NAME) to access your camera プロフィール画像をその場で撮影するために使用します。撮影した画像は端末内にのみ保存します。
NSUserTrackingUsageDescription This app needs access to your data. 広告の表示回数を端末ごとに調整するためだけに識別子を使用します。閲覧履歴の収集や第三者への販売は行いません。

型は共通です。どの資源を、何のために使い、何をしないか。三つ目の「何をしないか」を添えると、説明が一段はっきりします。読み取らない、送信しない、共有しない。書けることは意外と多いはずです。

app.json への反映はこうなります。

{
  "expo": {
    "plugins": [
      ["expo-image-picker", {
        "photosPermission": false,
        "savePhotosPermission": "選んだ壁紙を写真アプリのアルバムへ保存するために使用します。ライブラリ内の写真を読み取ることはありません。",
        "cameraPermission": false
      }]
    ]
  }
}

false を指定すると、その説明文自体が Info.plist に書き込まれなくなります。使っていない資源は、良い文言を書くのではなく宣言ごと落とすのが正解です。

もうひとつ、複数の言語で配信している場合の落とし穴があります。app.json に書けるのは一言語分だけで、他の言語向けの文言は InfoPlist.strings を用意して差し替える形になります。日本語だけ丁寧に書き直して英語が既定のまま残っていると、英語ロケールで確認された時点でまた同じ指摘が返ります。私はここで一度、二往復を無駄にしました。

書き直しの対象は、審査で使われる言語ではなく、配信しているすべての言語です。

出し直すときに、一行だけ添える

修正版をアップロードしたら、App Store Connect の「返信」欄に短く書き添えています。長い弁明は要りません。

「使用していない写真ライブラリの読み取り宣言を削除し、保存用の説明文に具体的な用途を記載しました」

自動解析が返した指摘に対して、どのキーをどう変えたかを一文で示す。それだけで、人間のレビュアーが確認する手間が減ります。私の場合、この形にしてから差し戻しの往復が一度で収まるようになりました。

次に提出する前に、audit-purpose-strings.mjs を一度走らせてみてください。実行そのものは一瞬で終わります。差し戻しを受けてから原因を探す時間と比べれば、比較になりません。

提出前に見ておきたい確認項目をもう少し広く揃えたい方は、生成ループが緑を返した後に、私が手元で回している4つの受け入れ検査に、成果物そのものから不具合を拾う方法をまとめています。Guideline ごとの差し戻し対応はRork で作ったアプリが App Store でリジェクトされた時の対処辞典が近いはずです。

私自身、purpose string を「通すために埋める欄」だと思っていた時期が長くありました。今は、アプリが何をして何をしないかを利用者へ一行で伝える場所だと考えています。読んでくださってありがとうございました。

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

アプリ開発2026-06-25
広告とサブスクを入れた途端に増える「Appプライバシー」の申告 — 実際にチェックを付けた項目の記録
AdMob・RevenueCat・Crashlytics を組み込んだ Rork(Expo) アプリで、App Store Connect の「Appプライバシー」に何をどう申告したか。トラッキングとATTの連鎖まで、6本運用の手元メモとして整理しました。
開発ツール2026-05-21
RorkでビルドしたiOSアプリがTestFlightで「ITMS-90683」拒否される — Info.plistの使用目的文字列の埋め方
Rork書き出しのiOSアプリをApp Store Connectに提出した直後、メールで「ITMS-90683: Missing Purpose String in Info.plist」を受け取る現象について、原因とapp.jsonからの恒久的な修正手順を、12年間の個人開発で繰り返しハマってきた立場から具体的に説明します。
アプリ開発2026-08-18
Rork のプロジェクトで targetSdkVersion 36 を通すまでに直した3か所
app.json に targetSdkVersion 36 と書いてあるのに、ビルドが読んでいた値は 35 でした。実効値を報告するスクリプトと、引き上げ・更新停止・延長申請の三択の分け方を残します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →