Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 39 additions & 6 deletions packages/core/application/application.ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
}
Comment on lines 388 to +393
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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 {

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);
Expand Down
Loading