From bcb87c9906de1a01e2e32cdee7113ef7684bc2d9 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Fri, 5 Jun 2026 17:03:57 -0700 Subject: [PATCH] fix(ios): re-attach root view after in-process runtime reload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the runtime is reloaded in-process (NativeScriptRuntime.reloadApplication, used for OTA soft reboots) the app re-runs main in a fresh JS isolate while the process stays alive. iOSApplication.run() sees a live UIApplication and routes to runAsEmbeddedApp() - The previous UIWindow was deallocated along with the old isolate (it was retained only by the JS-side SceneDelegate._window), and sceneWillConnectToSession — which creates the window — does not re-fire on a reload. getWindow() then returned null and runAsEmbeddedApp() bailed early, leaving a black screen. - With no embedder delegate (NativeScript owns the UIApplication), the new root was presented modally over the old, now-dead root controller instead of replacing it — leaking the old controller on every reload and breaking when an overlay is mid-presentation. runAsEmbeddedApp() now: - recreates a window bound to the active UIWindowScene when none is found (initWithWindowScene + _setWindowForScene/_setupWindowForScene); - moves the rootViewController guard into the embedder-only branch, since a freshly recreated window has no controller yet; - replaces the window root via setWindowRootView() for the NativeScript-owned case instead of modal-presenting over the stale root. --- packages/core/application/application.ios.ts | 45 +++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/packages/core/application/application.ios.ts b/packages/core/application/application.ios.ts index 8f44205b25..e5b8c735de 100644 --- a/packages/core/application/application.ios.ts +++ b/packages/core/application/application.ios.ts @@ -331,17 +331,43 @@ export class iOSApplication extends ApplicationCommon implements IiOSApplication this._rootView = rootView; setRootView(rootView); // Attach to the existing iOS app - const window = getWindow() as UIWindow; + let window = getWindow() as UIWindow; if (!window) { - return; + // In-process soft reboot with OTAs. + // Original UIWindow is deallocated when the old JS isolate is torn down. + // Recreate a window bound to the active UIWindowScene so the + // root has somewhere to attach. + const app = UIApplication.sharedApplication; + const all = app && app.connectedScenes ? app.connectedScenes.allObjects : null; + let targetScene: UIWindowScene; + if (all) { + for (let i = 0; i < all.count; i++) { + const s = all.objectAtIndex(i) as UIWindowScene; + // Only UIWindowScene exposes `windows`; prefer a foreground-active one. + if (s && typeof s.windows !== 'undefined') { + targetScene = s; + if (s.activationState === UISceneActivationState.ForegroundActive) { + break; + } + } + } + } + if (targetScene) { + window = UIWindow.alloc().initWithWindowScene(targetScene); + this._setWindowForScene(window, targetScene); + this._setupWindowForScene?.(window, targetScene); + } } - const rootController = window.rootViewController; - if (!rootController) { + if (!window) { return; } + // May be null on a freshly recreated window — expected; the replace-root + // path below sets it. Only the embedder path needs an existing controller. + const rootController = window.rootViewController; + const controller = this.getViewController(rootView); const embedderDelegate = NativeScriptEmbedder.sharedInstance().delegate; @@ -360,11 +386,18 @@ export class iOSApplication extends ApplicationCommon implements IiOSApplication }); if (embedderDelegate) { + // Embed into host app. + // present over the host's existing root view controller. + if (!rootController) { + return; + } this.setViewControllerView(rootView); embedderDelegate.presentNativeScriptApp(controller); } else { - const visibleVC = iosUtils.getVisibleViewController(rootController); - visibleVC.presentViewControllerAnimatedCompletion(controller, true, null); + // No embedder delegate = NativeScript owns the UIApplication. + // Attach the root to the window. + this.setViewControllerView(rootView); + this.setWindowRootView(window, rootView); } this.initRootView(rootView);