RORK LABJP
SDK58 — The Expo SDK 58 beta is open. It ships the React Native 0.88 release candidate, and the beta period is stated as three to four weeks11/01 — For anyone who requested an extension, Google Play's target API deadline lands on November 1. Forty-four days outEASENV — A long-open report: secrets handed to a local build arrive as the literal variable name rather than its value, and the damage surfaces much laterNEW — The replacement the table recommended had already shut down. A record of reconciling all 74 rows of the deprecation listUISCENE — iOS 27 requires the new scene lifecycle. SDK 57 makes it something you opt into; it only becomes the default in 58CREDIT — What "AI errors don't cost credits" actually covers becomes clear once you record a day of asking for the same fix more than onceSDK58 — The Expo SDK 58 beta is open. It ships the React Native 0.88 release candidate, and the beta period is stated as three to four weeks11/01 — For anyone who requested an extension, Google Play's target API deadline lands on November 1. Forty-four days outEASENV — A long-open report: secrets handed to a local build arrive as the literal variable name rather than its value, and the damage surfaces much laterNEW — The replacement the table recommended had already shut down. A record of reconciling all 74 rows of the deprecation listUISCENE — iOS 27 requires the new scene lifecycle. SDK 57 makes it something you opt into; it only becomes the default in 58CREDIT — What "AI errors don't cost credits" actually covers becomes clear once you record a day of asking for the same fix more than once
Articles/App Dev
App Dev/2026-09-18Intermediate

The three lines I check when prebuild stops on the standard SDK 57 Swift AppDelegate error

Turning on ios.enableSceneSupport for iOS 27 can stop prebuild cold. The cause was not my SDK version but the shape of AppDelegate.swift. Here are the three lines to check, and why copying the SDK 58 migration steps by hand will break your build.

Expo209expo-build-propertiesiOS 274prebuild3Rork568

I was working through iOS 27 support for my wallpaper app late one evening. I added ios.enableSceneSupport to app.json, ran npx expo prebuild --clean, and got back a single line.

ios.enableSceneSupport requires the standard Expo SDK 57 Swift AppDelegate

The word requires made me read it as a version problem. I bumped expo, ran npx expo install --fix, and hit the same line again. Then I stopped typing and read the log from the top.

The cause was not a version at all. It was the shape of ios/<project>/AppDelegate.swift.

The error points at your AppDelegate, not your SDK version

The scene support in expo-build-properties assumes it is editing the untouched SDK 57 template. It looks for exactly three things — the class declaration line, the window = UIWindow(...) line, and the factory.startReactNative(...) line. The earlier implementation matched all three as one contiguous string.

Now add the @react-native-firebase/app config plugin. It inserts FirebaseApp.configure() directly above factory.startReactNative(. That single inserted line breaks the contiguous match, so the plugin decides your AppDelegate is not the template and refuses to continue (expo/expo#50210).

// ios/YourApp/AppDelegate.swift (after the Firebase config plugin runs)
#if os(iOS) || os(tvOS)
  window = UIWindow(frame: UIScreen.main.bounds)
 
  // <- this one line broke the contiguous match
  FirebaseApp.configure()
 
  factory.startReactNative(withModuleName: "main", in: window, launchOptions: launchOptions)
#endif

So the subject of the error is neither the SDK nor Xcode. It is another plugin already living in your project. I bumped expo three times before that landed for me.

There is a reason the plugin is this strict. It is not parsing Swift into a syntax tree; it is finding strings and replacing them. When the match slips, throwing is safer than quietly continuing. A half-applied AppDelegate is the worst outcome of the three, because it compiles and then fails to launch — and that is the version of this problem that eats an entire evening.

The three lines to check

You can verify that the "standard AppDelegate" is still intact before you run prebuild. As an indie developer who adds and removes plugins constantly, I have found that one of these three lines quietly changes more often than you would expect.

# Only useful if you commit ios/ (with CNG, check after prebuild instead)
APP_DELEGATE=$(ls ios/*/AppDelegate.swift | head -1)
 
# 1. The class declaration (is it still subclassing ExpoAppDelegate?)
grep -n "class AppDelegate" "$APP_DELEGATE"
 
# 2. The window creation line
grep -n "window = UIWindow" "$APP_DELEGATE"
 
# 3. The React Native startup line
grep -n "startReactNative" "$APP_DELEGATE"
 
# Expected: each command returns exactly one line.
# If any returns nothing, the plugin gives up and throws.

A command that returns nothing is your answer. If all three return a line and prebuild still fails, something was inserted between them. grep -n -A2 "window = UIWindow" is the fastest way to see that with your own eyes.

When an upgrade is enough, and when you write it by hand

The fix for this landed in expo/expo#50221 and has been published to npm. The plugin no longer matches the block as one string; it removes the window statement and the startReactNative call individually, so lines another plugin inserted between them survive.

That makes the order simple: update expo-build-properties first, then run prebuild again. You also need expo@57.0.23 or newer, because that is the release where the scene runtime was backported to SDK 57. Older 57 patches make the plugin throw, so run npx expo install --fix before anything else. Expo's own write-up is the iOS scene life cycle guide.

One more thing to settle before you decide your order. You need this opt-in when you build with Xcode 27 and the iOS 27 SDK. At the time of writing, the EAS Build latest image is still on Xcode 26.6, with 27 listed as coming soon. So this is a step the people testing on a local Xcode 27 hit first, and if you build only in the cloud you have a little more room. Waiting for the SDK 58 stable release and taking the default is a perfectly reasonable call too — I may be missing something, but I would rather not hand-edit a native file I do not have to.

If you still stop on the same line, two possibilities remain: you hand-edit AppDelegate.swift, or your Info.plist already declares a scene manifest. In both cases the plugin refuses to overwrite your work and asks you to apply the change yourself.

Do not copy the SDK 58 steps by hand

This is where I misread things most expensively. The same guide has a hand-migration section telling you to create SceneDelegate.swift and point UISceneDelegateClassName at $(PRODUCT_MODULE_NAME).SceneDelegate. I copied that, and produced a build that would not launch.

Those are the SDK 58 steps. The SDK 57 opt-in generates no SceneDelegate.swift at all. What you point at is Expo's built-in EXExpoAppSceneDelegate.

<!-- ios/YourApp/Info.plist - hand-applied on 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>
        <!-- NOT $(PRODUCT_MODULE_NAME).SceneDelegate, which is the SDK 58 form -->
        <key>UISceneDelegateClassName</key>
        <string>EXExpoAppSceneDelegate</string>
      </dict>
    </array>
  </dict>
</dict>

Same key, different target depending on the release. Side by side, only three things differ.

ItemSDK 57 (opt-in)SDK 58 (default)
How it turns onios.enableSceneSupport: trueOn by default; the property is a no-op with a warning
SceneDelegate.swiftNot generatedGenerated by prebuild
UISceneDelegateClassNameEXExpoAppSceneDelegate$(PRODUCT_MODULE_NAME).SceneDelegate

What the property actually changes

With true, prebuild touches three places. It makes your AppDelegate conform to ExpoReactNativeFactoryProvider, removes the legacy startup block from didFinishLaunchingWithOptions, and adds a scene manifest to Info.plist pointing at EXExpoAppSceneDelegate. Setting the property back to false reverts all three. Put the other way round: if you have already hand-managed any of those three places, the assumption the plugin works under no longer holds, and it will tell you so rather than guess.

Once you are on the scene life cycle, UIKit stops calling several AppDelegate methods. application(_:open:options:) for URLs, application(_:continue:restorationHandler:) for Handoff, the whole foreground and background set from applicationDidBecomeActive(_:) through applicationWillEnterForeground(_:), and application(_:performActionFor:completionHandler:) for quick actions.

That is the fork in the road. If a config plugin is written against ExpoAppDelegateSubscriber, Expo forwards the scene events to it and nothing needs changing. Overrides on ExpoAppDelegate also keep working as long as you keep calling super. What needs attention is code that grabs UIApplicationDelegate directly — a library waiting for UIKit to call applicationDidBecomeActive(_:) on the app delegate, for instance. That code moves to the scene equivalent such as sceneDidBecomeActive(_:), or over to a subscriber.

For the day you set it back to false

Sometimes you turn the property off again, and that path had its own step. Disabling it used to reinsert the whole startup block below the factory assignment, which left lines other plugins had put inside the #if os(iOS) wrapper stranded in a second wrapper underneath. The second commit in the same PR fixes that too.

It is worth noting where to look when the property applies but the app will not start. A black screen on iOS 27 usually means the scene manifest is missing or UISceneDelegateClassName does not resolve. If the app opens but Linking.getInitialURL() returns null, suspect cold-start URL delivery instead. And if the same URL arrives twice, you still have a manual path handing it to RCTLinkingManager from your own override.

Before you turn a setting on, read the lines that setting reads. For a while I kept placing the subject of the error on the tool's side, and that got me nowhere. What works for me now is opening the files a config plugin touches before I add it, and checking whether someone already edited them — the same habit as running my site gates in a fixed order.

There is one thing worth doing today. Read plugins in your app.json from the top and mark the ones that touch AppDelegate. That alone narrows the search the next time prebuild stops.

If you want to count what disappears before you upgrade, I wrote that up in Find the native edits expo prebuild will erase before you upgrade to SDK 57, and the device-side check lives in Put Your Live Rork App on a Spare iPhone Before iOS 27 Ships. If you would rather make the inventory itself repeatable, Counting what prebuild --clean will erase before you upgrade to Expo SDK 57 is the closest thing I have.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

App Dev2026-09-16
The Token I Deleted Came Back After a Restart — Detecting Failed SecureStore Deletes
On Android, getItemAsync can report null right after sign-out while the value is still sitting on disk. Here is how I rewrote a sign-out path that was verifying deletion by reading instead of by the delete result.
App Dev2026-09-10
Your First EAS Workflow in a Rork Repo, and the Alert That Goes Missing Exactly When You Need It
Putting two files into .eas/workflows in a repo exported from Rork, and why a notification wired with needs stays silent on exactly the nights it fails — with the output of a small local checker I actually ran.
App Dev2026-08-25
Put Your Live Rork App on a Spare iPhone Before iOS 27 Ships
iOS 27 arrives in September and developer beta 7 is already out. Here is how to turn an old iPhone into a beta device, what to check first, and which fixes you can ship from JavaScript without waiting for review.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links