Why this happens
Apple’s iOS 27 SDK, shipped with Xcode 27, enforces the UIScene (scene-based) application lifecycle for every app built against it. Apps that still use the legacy UIApplicationDelegate–only lifecycle—where the window is created and held in the app delegate—fail immediately at launch with the runtime error:
failure in void _UIApplicationEvaluateRuntimeIssueForNoSceneLifecycleAdoption(void)_block_invoke
(UIApplication_RuntimeIssues.m:106) :
Application failed to launch: UIScene life cycle is required for apps built with this SDK.
React Native 0.85 still ships a template that uses the classic AppDelegate entry point. When you build that template with Xcode 27, the resulting binary is linked against the iOS 27 SDK but lacks the scene manifest and scene delegate the SDK now demands. The result is a successful build that crashes before any JavaScript runs.
Apple’s Technical Note TN3187 documents this requirement. App Store submissions must use the iOS 27 SDK starting April 2027, so staying on Xcode 26 is only a temporary workaround.
How to fix it
1. Add a SceneDelegate (Objective-C or Swift)
Create a new SceneDelegate class that conforms to UIWindowSceneDelegate. In a React Native project the delegate’s job is to create the window, hand it to RCTReactNativeFactory, and forward scene-level callbacks (URLs, user activities) so deep links and universal links keep working.
Objective-C (SceneDelegate.h)
#import <UIKit/UIKit.h>
#import <React/RCTReactNativeFactory.h>
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
@property (nonatomic, strong) UIWindow *window;
@end
Objective-C (SceneDelegate.m)
#import "SceneDelegate.h"
#import <React/RCTReactNativeFactory.h>
#import <React/RCTLinkingManager.h>
@implementation SceneDelegate
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
UIWindowScene *windowScene = (UIWindowScene *)scene;
self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
// Use the factory from AppDelegate (see step 2)
id<RCTReactNativeFactory> factory = [(id)[UIApplication sharedApplication].delegate reactNativeFactory];
[factory startReactNativeWithModuleName:@"YourAppName" inWindow:self.window launchOptions:connectionOptions];
// Forward deep-link / universal-link payloads
if (connectionOptions.URL) {
[RCTLinkingManager application:[UIApplication sharedApplication] openURL:connectionOptions.URL options:@{}];
}
for (NSUserActivity *activity in connectionOptions.userActivities) {
[RCTLinkingManager application:[UIApplication sharedApplication] continueUserActivity:activity restorationHandler:nil];
}
[self.window makeKeyAndVisible];
}
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
for (UIOpenURLContext *ctx in URLContexts) {
[RCTLinkingManager application:[UIApplication sharedApplication] openURL:ctx.URL options:@{}];
}
}
- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity {
[RCTLinkingManager application:[UIApplication sharedApplication] continueUserActivity:userActivity restorationHandler:nil];
}
@end
Swift (SceneDelegate.swift) — if your project uses Swift:
import UIKit
import React
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { return }
let window = UIWindow(windowScene: windowScene)
self.window = window
guard let factory = (UIApplication.shared.delegate as? AppDelegate)?.reactNativeFactory else { return }
factory.startReactNative(withModuleName: "YourAppName", in: window, launchOptions: connectionOptions)
if let url = connectionOptions.urlContexts.first?.url {
RCTLinkingManager.application(UIApplication.shared, open: url, options: [:])
}
for activity in connectionOptions.userActivities {
RCTLinkingManager.application(UIApplication.shared, continue: activity, restorationHandler: nil)
}
window.makeKeyAndVisible()
}
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
for ctx in URLContexts {
RCTLinkingManager.application(UIApplication.shared, open: ctx.url, options: [:])
}
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
RCTLinkingManager.application(UIApplication.shared, continue: userActivity, restorationHandler: nil)
}
}
2. Update AppDelegate to expose the factory and keep a window reference
Legacy third-party libraries sometimes call [UIApplication sharedApplication].delegate.window. Keep a window property on AppDelegate and set it from SceneDelegate until those libraries are updated.
Objective-C (AppDelegate.h)
#import <UIKit/UIKit.h>
#import <React/RCTReactNativeFactory.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTReactNativeFactory>
@property (nonatomic, strong) UIWindow *window; // legacy accessor
@end
Objective-C (AppDelegate.m) — remove any window creation code from application:didFinishLaunchingWithOptions:; the factory methods stay the same.
3. Configure Info.plist with the scene manifest
Add a UIApplicationSceneManifest dictionary. For a single-window React Native app set UIApplicationSupportsMultipleScenes to false. The critical key is UISceneDelegateClassName:
- Swift: use the module-qualified form
$(PRODUCT_MODULE_NAME).SceneDelegate - Objective-C: use the bare class name
SceneDelegate(the module-qualified form fails with “Info.plist configuration ‘(no name)’ for UIWindowSceneSessionRoleApplication…”)
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>SceneDelegate</string> <!-- Objective-C -->
<!-- <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string> Swift -->
</dict>
</array>
</dict>
</dict>
4. (Optional) Upgrade to React Native 0.88+
React Native 0.88 (released October 2026) adds first-class UISceneDelegate support: RCTReactNativeFactory gains startReactNativeWithModuleName:inWindow:connectionOptions: and the template includes a ready-made SceneDelegate.swift plus the correct Info.plist entries. If you can upgrade, follow the 0.88 upgrade guide and you’ll get the scene lifecycle automatically.
For Expo projects: SDK 58 enables scenes by default; on SDK 57 add ios.enableSceneSupport: true to expo-build-properties and run npx expo prebuild --clean.
How to prevent it
- Test on Xcode beta / RC as soon as a new major Xcode ships; the scene requirement appeared in Xcode 27 beta.
- Pin your CI to a known-good Xcode version until you’ve validated the scene migration.
- Audit third-party native modules for direct
AppDelegate.windowaccess; file issues or PRs to migrate them. - Keep React Native and Expo SDKs current—official scene support arrived in RN 0.88 / Expo SDK 58.
FAQ
Does this affect apps built with Xcode 26 but running on iOS 27?
No. The enforcement happens at build time against the iOS 27 SDK. An app built with Xcode 26 (iOS 26 SDK) continues to launch on iOS 27 devices, but you’ll need to migrate before the April 2027 App Store deadline.
Why does UISceneDelegateClassName fail with $(PRODUCT_MODULE_NAME).SceneDelegate in Objective-C?
Objective-C classes are not namespaced by the Swift module. The runtime looks up the class by its global name (SceneDelegate). The module-qualified string is only valid for Swift classes.
Can I keep supporting multiple scenes later?
Yes. Set UIApplicationSupportsMultipleScenes to true and implement application:configurationForConnectingSceneSession:options: in AppDelegate to return per-scene configurations. React Native 0.88 currently supports a single scene; multi-window support is on the roadmap.
Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.
