Repository navigation
showOverlay creates a scene-less UIWindow, so overlays never appear under UIScene lifecycle #8345
Description
Activity
Root cause
RNNCommandsHandler.showOverlay:(current):[overlayVC setReactViewReadyCallback:^{ UIWindow *overlayWindow = [[RNNOverlayWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; overlayWindow.rootViewController = weakOverlayVC; [self->_overlayManager showOverlayWindow:overlayWindow]; // ... }]; [overlayVC render];
RNNOverlayManagerthen only doessetHidden:NO. That is not enough for a window with no scene.Compare with
setRoot:, which mutates the already scene-connected_mainWindow:[vc setReactViewReadyCallback:^{ self->_mainWindow.rootViewController = weakVC; }];
Extra detail (New Arch): overlay VCs do not set
waitForRender, soreactViewReadyCallbackfires immediately and then creates this window. This is not “callback never called”. The callback creates the wrong kind of window.ReactNativeNavigation.mainWindowcan also create a scene-less window ifdelegate.windowis nil:keyWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
That is a related footgun for scene-based hosts, but the overlay bug happens even when
_mainWindowis already scene-connected.Suggested fix
Create overlay windows with the main window’s scene:
RNNOverlayWindow *overlayWindow; UIWindowScene *windowScene = self->_mainWindow.windowScene; if (windowScene) { overlayWindow = [[RNNOverlayWindow alloc] initWithWindowScene:windowScene]; overlayWindow.frame = self->_mainWindow.bounds; } else { overlayWindow = [[RNNOverlayWindow alloc] initWithFrame:self->_mainWindow.bounds]; } overlayWindow.rootViewController = weakOverlayVC;
We patched this locally against
8.8.9and overlays became visible. Using_mainWindow.windowSceneis important so the overlay lands on the same scene RNN already owns.Please also consider:
- Same
initWithWindowScene:path for any other extra windows RNN creates. - Avoid
initWithFrame:fallback on iOS 13+ when a scene exists — that window will stay invisible. - In
mainWindow, ifdelegate.windowis missing, prefer a connectedUIWindowSceneoverinitWithFrame:.
Reacted by sami4064, Donato Aguirre and MrKinq- Same
- changed the title
[-]`showOverlay` creates a scene-less `UIWindow`, so overlays never appear under UIScene lifecycle (iOS 13+, required on iOS 26/27)[/-][+]`showOverlay` creates a scene-less `UIWindow`, so overlays never appear under UIScene lifecycle[/+]on Aug 27, 2026 Root cause
RNNCommandsHandler.showOverlay:(current):[overlayVC setReactViewReadyCallback:^{
UIWindow *overlayWindow =
[[RNNOverlayWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
overlayWindow.rootViewController = weakOverlayVC;
[self->_overlayManager showOverlayWindow:overlayWindow];
// ...
}];
[overlayVC render];
RNNOverlayManagerthen only doessetHidden:NO. That is not enough for a window with no scene.Compare with
setRoot:, which mutates the already scene-connected_mainWindow:[vc setReactViewReadyCallback:^{
self->_mainWindow.rootViewController = weakVC;
}];
Extra detail (New Arch): overlay VCs do not setwaitForRender, soreactViewReadyCallbackfires immediately and then creates this window. This is not “callback never called”. The callback creates the wrong kind of window.ReactNativeNavigation.mainWindowcan also create a scene-less window ifdelegate.windowis nil:keyWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
That is a related footgun for scene-based hosts, but the overlay bug happens even when_mainWindowis already scene-connected.Suggested fix
Create overlay windows with the main window’s scene:
RNNOverlayWindow *overlayWindow;
UIWindowScene *windowScene = self->_mainWindow.windowScene;
if (windowScene) {
overlayWindow = [[RNNOverlayWindow alloc] initWithWindowScene:windowScene];
overlayWindow.frame = self->_mainWindow.bounds;
} else {
overlayWindow = [[RNNOverlayWindow alloc] initWithFrame:self->_mainWindow.bounds];
}
overlayWindow.rootViewController = weakOverlayVC;
We patched this locally against8.8.9and overlays became visible. Using_mainWindow.windowSceneis important so the overlay lands on the same scene RNN already owns.Please also consider:
- Same
initWithWindowScene:path for any other extra windows RNN creates. - Avoid
initWithFrame:fallback on iOS 13+ when a scene exists — that window will stay invisible. - In
mainWindow, ifdelegate.windowis missing, prefer a connectedUIWindowSceneoverinitWithFrame:.
I hit the same mainWindow footgun on scene-based apps and fixed it locally in ReactNativeNavigation.mm by preferring an already connected UIWindowScene instead of creating a scene-less UIWindow with initWithFrame:.
This is the patch I’m currently using:
- (UIWindow *)mainWindow { UIWindow *keyWindow = UIApplication.sharedApplication.delegate.window; if (keyWindow) { return keyWindow; } if (@available(iOS 13.0, *)) { for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) { if (![scene isKindOfClass:[UIWindowScene class]]) { continue; } UIWindowScene *windowScene = (UIWindowScene *)scene; if (windowScene.activationState == UISceneActivationStateUnattached) { continue; } keyWindow = [[UIWindow alloc] initWithWindowScene:windowScene]; keyWindow.frame = windowScene.coordinateSpace.bounds; UIApplication.sharedApplication.delegate.window = keyWindow; return keyWindow; } } // Legacy fallback only. With UIScene configured this should not normally be reached on iOS 13+ once the scene has connected. keyWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; UIApplication.sharedApplication.delegate.window = keyWindow; return keyWindow; }With this change, the RNN main window is attached to a real UIWindowScene when delegate.window is still nil.
- Same
What happened?
On iOS,
Navigation.showOverlay()creates a newRNNOverlayWindowwithinitWithFrame:and never assigns aUIWindowScene.With a scene-based app lifecycle, that window is not added to the scene, so the overlay never appears. JS still mounts and runs (the overlay VC is created and rendered), which makes this look like a hang: native/RNN splash dismisses, overlay logic is running, but the screen stays on
RNNSplashScreenViewController/ the current root.Navigation.setRoot()is not affected, because it attaches the layout to the existing_mainWindowthat the host already bound to the scene.This is easy to miss on older iOS if the app still used the AppDelegate window path. It becomes a hard break once the app adopts
UIWindowScene(mandatory on iOS 26+).What was the expected behaviour?
showOverlayshows the overlay above the current root on the sameUIWindowScene.Was it tested on latest react-native-navigation?
Help us reproduce this issue!
UIApplicationSceneManifest+UIWindowSceneDelegate).initWithWindowScene:and pass it to RNN (delegate.windowset before bootstrap).Navigation.events().registerAppLaunchedListener, do notsetRoot. Call:componentDidMount/useEffectruns) but nothing is visible. The scene window is stillRNNSplashScreenViewController.Control: the same screen shown via
setRootappears correctly:In what environment did this happen?
8.8.90.87(New Architecture / Fabric, bridgeless)UIScene)RNNAppDelegate+ customUIWindowSceneDelegate