RORK LABEN
SDK58 — Expo SDK 58 のベータが始まりました。React Native 0.88 の RC を同梱し、ベータ期間は3〜4週間と公式に書かれています11/01 — Google Play の対象 API レベル、延長を申請した場合の配信期限は11月1日です。残り44日ですEASENV — ローカルビルドに渡したはずの秘密が、中身ではなく変数名の文字列のまま届く、という報告が長く開いたままですNEW — 推奨された移行先が、すでに停止していました。廃止表を74行突き合わせた記録ですUISCENE — iOS 27 では新しい画面ライフサイクルが必須です。SDK 57 では自分で有効にする設定で、既定になるのは 58 からですCREDIT — 「AI のエラーには消費しない」がどこまでを指すのかは、同じ修正を何度か頼んだ日の記録を取ると見えてきますSDK58 — Expo SDK 58 のベータが始まりました。React Native 0.88 の RC を同梱し、ベータ期間は3〜4週間と公式に書かれています11/01 — Google Play の対象 API レベル、延長を申請した場合の配信期限は11月1日です。残り44日ですEASENV — ローカルビルドに渡したはずの秘密が、中身ではなく変数名の文字列のまま届く、という報告が長く開いたままですNEW — 推奨された移行先が、すでに停止していました。廃止表を74行突き合わせた記録ですUISCENE — iOS 27 では新しい画面ライフサイクルが必須です。SDK 57 では自分で有効にする設定で、既定になるのは 58 からですCREDIT — 「AI のエラーには消費しない」がどこまでを指すのかは、同じ修正を何度か頼んだ日の記録を取ると見えてきます
記事一覧/アプリ開発
アプリ開発/2026-09-18中級

prebuild が requires the standard Expo SDK 57 Swift AppDelegate で止まったとき、まず確かめる3行

iOS 27 のために ios.enableSceneSupport を有効にすると prebuild が止まることがあります。原因は SDK の版ではなく AppDelegate.swift の形でした。確かめる3行と、手で書くときに SDK 58 の手順を真似てはいけない理由をまとめます。

Expo209expo-build-propertiesiOS 274prebuild3Rork568

壁紙アプリの iOS 27 対応を進めていた夜、app.jsonios.enableSceneSupport を足して npx expo prebuild --clean を回しました。返ってきたのは一行だけでした。

ios.enableSceneSupport requires the standard Expo SDK 57 Swift AppDelegate

requires という語を見て、私は「版が足りないのだ」と読みました。expo を上げ直し、npx expo install --fix も通しました。それでも同じ行で止まります。手が止まったまま、ログを上から読み直しました。

原因は版ではなく、ios/<プロジェクト名>/AppDelegate.swift の形のほうにありました。requires が指していたのは「版」ではなく「形」だったのだと気づいたのは、三度目の再実行が空振りしたあとでした。

エラーが指しているのは版ではなく AppDelegate の形です

expo-build-properties のシーン対応は、SDK 57 のテンプレートそのままの AppDelegate を書き換える前提で作られています。プラグインが探すのは三つだけです——クラス宣言の行、window = UIWindow(...) の行、そして factory.startReactNative(...) の行です。以前の実装は、この三つをひと続きの文字列として照合していました。

ここに @react-native-firebase/app の config plugin が入ると、FirebaseApp.configure()factory.startReactNative( のすぐ上へ挿し込まれます。挿された一行でひと続きの一致が崩れ、プラグインは「テンプレートではない」と判断して止まっていたのです(expo/expo#50210)。

// ios/YourApp/AppDelegate.swift(Firebase の config plugin が入ったあと)
#if os(iOS) || os(tvOS)
  window = UIWindow(frame: UIScreen.main.bounds)
 
  // ← この一行が挿し込まれ、ひと続きの照合が崩れていました
  FirebaseApp.configure()
 
  factory.startReactNative(withModuleName: "main", in: window, launchOptions: launchOptions)
#endif

つまりエラーの主語は SDK でも Xcode でもなく、自分のプロジェクトに入っている別のプラグインでした。私自身、ここに気づくまで expo の版を三度上げ直しております。

プラグインがここまで厳しいのには理由があります。Swift のコードを構文木として解析しているのではなく、文字列として探して差し替えているのです。ですから照合が外れたときに黙って書き換えを続けるより、例外を投げて止まるほうが安全です。中途半端に当たった AppDelegate は、ビルドは通るのに起動しない——という、いちばん時間を奪われるかたちで表に出てまいります。

まず確かめる3行

プラグインの言う「標準の AppDelegate」が手元に残っているかは、prebuild を回す前に確かめられます。個人開発でプラグインを足したり外したりしていると、この三行のどれかが静かに書き換わっていることがあるのです。

# ios/ をコミットしている場合のみ有効です(CNG なら prebuild 後に確認します)
APP_DELEGATE=$(ls ios/*/AppDelegate.swift | head -1)
 
# 1. クラス宣言の行(ExpoAppDelegate を継承しているか)
grep -n "class AppDelegate" "$APP_DELEGATE"
 
# 2. window の生成行
grep -n "window = UIWindow" "$APP_DELEGATE"
 
# 3. React Native の起動行
grep -n "startReactNative" "$APP_DELEGATE"
 
# 期待する出力: 3 つのコマンドがそれぞれ 1 行を返します
# どれかが 0 行なら、プラグインは書き換えを諦めて例外を投げます

grep が何も返さない行があれば、そこが止まっている理由です。三行とも返るのに止まる場合は、行と行のあいだに誰かが挿し込んでいます。grep -n -A2 "window = UIWindow" で前後を目で見るのがいちばん早い確認になります。

上げれば済む場合と、手で書く場合

この件の修正は expo/expo#50221 で取り込まれ、npm へも公開されています。プラグインは三つをひと続きで照合するのをやめ、window の文と startReactNative の文を個別に外すようになりました。あいだに挿された行はそのまま残ります。

ですから順番としては、expo-build-properties を最新にして prebuild を回し直すのが先です。前提として expo@57.0.23 以上が必要で、シーンのランタイムが SDK 57 へ戻し入れられたのはこの版です。それより古い 57 系ではプラグインが例外を投げますので、npx expo install --fix を先に通します。

もう一つ、順番を決める前に確かめておきたいことがあります。この opt-in が必要になるのは、Xcode 27 と iOS 27 SDK でビルドする場合です。EAS Build の latest イメージは執筆時点で Xcode 26.6 のままで、27 は近日とされています。つまり手元の Xcode 27 で試している方が先に踏む段差で、クラウドのビルドだけで回している方はもう少し猶予があるのです。急いで立てるより、SDK 58 の安定版を待って既定に乗るという選択も十分に筋が通ります。

それでも同じ行で止まるなら、残っている可能性は二つです。AppDelegate.swift を自分の手で編集している場合と、Info.plist に既にシーンマニフェストを書いている場合です。どちらもプラグインは上書きを拒み、手で適用してほしいと告げてきます。公式の案内は Expo の iOS scene life cycle ガイドにまとまっています。

手で書くときに SDK 58 の手順をそのまま真似ないこと

ここが私のいちばんの読み違いでした。同じガイドの中に手で移行する節があり、そこには SceneDelegate.swift を自分で作り、UISceneDelegateClassName$(PRODUCT_MODULE_NAME).SceneDelegate を書くと案内されています。私はそれを写して、起動しないビルドを一本作りました。

あれは SDK 58 の手順です。SDK 57 の opt-in では SceneDelegate.swift は生成されません。指すべきは Expo に組み込まれた EXExpoAppSceneDelegate のほうなのです。

<!-- ios/YourApp/Info.plist — SDK 57 で手で入れる場合 -->
<key>UIApplicationSceneManifest</key>
<dict>
  <key>UIApplicationSupportsMultipleScenes</key>
  <false/>
  <key>UISceneConfigurations</key>
  <dict>
    <key>UIWindowSceneSessionRoleApplication</key>
    <array>
      <dict>
        <key>UISceneConfigurationName</key>
        <string>Default Configuration</string>
        <!-- SDK 58 の $(PRODUCT_MODULE_NAME).SceneDelegate ではありません -->
        <key>UISceneDelegateClassName</key>
        <string>EXExpoAppSceneDelegate</string>
      </dict>
    </array>
  </dict>
</dict>

同じ文字列を書く場所なのに、指す先が版で変わります。並べると違いは三つだけです。

項目SDK 57(opt-in)SDK 58(既定)
有効化の方法ios.enableSceneSupport: true既定で有効(プロパティは無効化され警告のみ)
SceneDelegate.swift生成されませんprebuild が生成します
UISceneDelegateClassNameEXExpoAppSceneDelegate$(PRODUCT_MODULE_NAME).SceneDelegate

このプロパティが実際に書き換えるもの

true にしたときに prebuild が触るのは三箇所です。AppDelegate を ExpoReactNativeFactoryProvider に準拠させ、didFinishLaunchingWithOptions から従来の起動処理を外し、Info.plistEXExpoAppSceneDelegate を指すシーンマニフェストを足します。false に戻せば三箇所とも元へ戻ります。逆に言えば、この三箇所のどれかを自分で手当てしているプロジェクトでは、プラグインに任せる前提が崩れているということです。

シーンのライフサイクルへ移ると、UIKit は AppDelegate のいくつかのメソッドを呼ばなくなります。URL を受ける application(_:open:options:)、Handoff の application(_:continue:restorationHandler:)、そして applicationDidBecomeActive(_:) から applicationWillEnterForeground(_:) までの前後面の通知がそれに当たります。クイックアクションの application(_:performActionFor:completionHandler:) も同じ扱いです。

ここが分かれ道になります。config plugin が ExpoAppDelegateSubscriber を使って書かれているなら、シーン側のイベントは Expo が転送してくれますので、そのままで動きます。ExpoAppDelegate の上でメソッドを override している場合も、super を呼び続けていれば転送されます。手当てが要るのは、UIApplicationDelegate を直に握っているコード——たとえば UIKit が呼んでくれる前提で applicationDidBecomeActive(_:) を待っているライブラリだけです。そうしたコードは sceneDidBecomeActive(_:) 相当へ移すか、購読側へ寄せることになります。

false に戻す日のために

一度立てたプロパティを戻すこともあります。その戻し方にも段差がありました。無効化したときに起動処理をまとめて factory の代入の下へ書き戻す実装だったため、他のプラグインが #if os(iOS) のラッパーの中に挿していた行が、下に別のラッパーとして取り残されていたのです。こちらも同じ PR の二つ目のコミットで直っています。

立ったのに起動しないときの見どころも、あわせて書き残しておきます。iOS 27 で真っ黒な画面のまま止まるなら、シーンマニフェストが入っていないか、UISceneDelegateClassName の綴りが解決できていない場合がほとんどです。アプリは開くのに Linking.getInitialURL()null を返すなら、コールドスタート時の URL 受け渡しのほうを疑います。同じ URL が二度届くときは、自分の override が RCTLinkingManager へ渡している手動の経路が残っています。

設定を立てる前に、その設定が読む行を自分の目で見ておきます。 最初のうち、私はエラー文の主語を製品側に置きすぎました。結果は芳しくありません。いまは config plugin を足す前にそれが触るファイルを開き、既に誰かが手を入れていないかを確かめる順番にしております。Lab のサイトを更新するときにゲートを決まった順で通しているのと、やっていることは同じです。

今日できることは一つだけです。app.jsonplugins を上から読み、AppDelegate を触るものに印を付けてください。それだけで、次に prebuild が止まったときの捜索範囲がぐっと狭まります。

上げる前に何が消えるのかを数える手順はExpo SDK 57 に上げる前に、prebuild で消えるネイティブ変更を洗い出すに、公開中のアプリを実機で先に触る方法はiOS 27 が正式版になる前に、予備の iPhone で公開中の Rork アプリを触っておくに書き残しております。棚卸しそのものを仕組みにしたい方はprebuild --clean で消える手入れを、上げる前に数える — Expo SDK 57 移行の棚卸しが近いところです。

同じ一行で手が止まっている方の、夜が少し短くなれば嬉しく思います。

シェア

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

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

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

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

関連記事

アプリ開発2026-09-16
消したはずのトークンが再起動で戻る — SecureStore の削除失敗を検知する設計
サインアウト直後に getItemAsync が null を返しても、Android のディスクには値が残っていることがあります。削除の成否を読み出しで確かめていた実装を、戻り値で判断する形へ書き直した記録です。
アプリ開発2026-09-10
Rork のリポジトリに置く最初の EAS Workflow と、失敗した回にだけ届かない通知
Rork から書き出したリポジトリに .eas/workflows を二本置くまでの手順と、needs でつないだ通知が失敗した回にだけ届かなくなる理由を、手元で走る検査スクリプトの実行結果つきで書き残します。
アプリ開発2026-08-25
iOS 27 が正式版になる前に、予備の iPhone で公開中の Rork アプリを触っておく
iOS 27 の正式版は9月に来ます。開発者ベータが7回目まで進んだ今のうちに、予備の iPhone へベータを入れて公開中のアプリを触る手順と、見る順番、JS 側で先に潰せる箇所をまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます