fix(ios): re-attach root view after in-process runtime reload#11261
fix(ios): re-attach root view after in-process runtime reload#11261NathanWalker wants to merge 1 commit into
Conversation
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.
|
View your CI Pipeline Execution ↗ for commit bcb87c9
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗ ☁️ Nx Cloud last updated this comment at |
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
Looking for one thing? Review this PR in Change Stack to search files, summaries, diffs, and code without losing your place. 📝 WalkthroughWalkthroughThis PR updates embedded NativeScript app initialization to handle missing UIWindow by recreating it bound to an active UIWindowScene, tolerate null root view controller during startup, and adjust view attachment strategies based on embedder presence versus direct owning-app mode. ChangesEmbedded app window initialization
Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| if (embedderDelegate) { | ||
| // Embed into host app. | ||
| // present over the host's existing root view controller. | ||
| if (!rootController) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
Partial initialization before early return may leave inconsistent state.
When embedderDelegate exists but rootController is null, the early return at line 392 occurs after rootView._setupAsRootView({}) (line 374) and event handler registration (lines 376-386) have already executed. This leaves this._rootView pointing to a view that was set up but never passed through initRootView() or notifyAppStarted().
Consider moving the rootController guard earlier (before _setupAsRootView and event handlers), or document that this partial state is intentionally handled on the next reload cycle.
🛠️ Suggested fix: Move guard earlier
+ const rootController = window.rootViewController;
+ const embedderDelegate = NativeScriptEmbedder.sharedInstance().delegate;
+
+ // Embed into host app requires an existing root view controller
+ if (embedderDelegate && !rootController) {
+ return;
+ }
+
const controller = this.getViewController(rootView);
- const embedderDelegate = NativeScriptEmbedder.sharedInstance().delegate;
rootView._setupAsRootView({});
rootView.on(IOSHelper.traitCollectionColorAppearanceChangedEvent, () => {
const userInterfaceStyle = controller.traitCollection.userInterfaceStyle;
const newSystemAppearance = this.getSystemAppearanceValue(userInterfaceStyle);
this.setSystemAppearance(newSystemAppearance);
});
rootView.on(IOSHelper.traitCollectionLayoutDirectionChangedEvent, () => {
const layoutDirection = controller.traitCollection.layoutDirection;
const newLayoutDirection = this.getLayoutDirectionValue(layoutDirection);
this.setLayoutDirection(newLayoutDirection);
});
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 {
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()
runAsEmbeddedApp() works same as before but now supports this use case:
Supports 9.1 runtime features for OTA installed hot reloading of runtime isolates.