From f8486a2935b40c2cc015087921dee0627c34c96f Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Thu, 6 Nov 2025 13:04:47 +0200 Subject: [PATCH 1/4] fix(ios): Allow creating missing view controllers on reused views --- packages/core/ui/frame/index.ios.ts | 13 +++++++--- packages/core/ui/page/index.ios.ts | 10 ++++++-- packages/core/ui/tab-view/index.ios.ts | 35 ++++++++++++++++++-------- 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/packages/core/ui/frame/index.ios.ts b/packages/core/ui/frame/index.ios.ts index 0d0f1f01b9..ef644696c5 100644 --- a/packages/core/ui/frame/index.ios.ts +++ b/packages/core/ui/frame/index.ios.ts @@ -27,10 +27,11 @@ let navControllerDelegate: UINavigationControllerDelegate = null; export class Frame extends FrameBase { viewController: UINavigationControllerImpl; - public _ios: iOSFrame; iosNavigationBarClass: typeof NSObject; iosToolbarClass: typeof NSObject; + private _ios: iOSFrame; + constructor() { super(); this._ios = new iOSFrame(this); @@ -43,7 +44,13 @@ export class Frame extends FrameBase { this._pushInFrameStack(); } - return this.viewController.view; + // View controller can be disposed during view disposal, so make sure to create a new one if not defined + if (!this._ios) { + this._ios = new iOSFrame(this); + this.viewController = this._ios.controller; + } + + return this._ios.controller.view; } public disposeNativeView() { @@ -726,9 +733,7 @@ export function _getNativeCurve(transition: NavigationTransition): UIViewAnimati return UIViewAnimationCurve.EaseInOut; } -/* tslint:disable */ class iOSFrame implements iOSFrameDefinition { - /* tslint:enable */ private _controller: UINavigationControllerImpl; private _showNavigationBar: boolean; private _navBarVisibility: 'auto' | 'never' | 'always' = 'auto'; diff --git a/packages/core/ui/page/index.ios.ts b/packages/core/ui/page/index.ios.ts index ee2b2519c0..2b5c46eb30 100644 --- a/packages/core/ui/page/index.ios.ts +++ b/packages/core/ui/page/index.ios.ts @@ -377,7 +377,13 @@ export class Page extends PageBase { } createNativeView() { - return this.viewController.view; + // View controller can be disposed during view disposal, so make sure to create a new one if not defined + if (!this._ios) { + const controller = UIViewControllerImpl.initWithOwner(new WeakRef(this)); + controller.view.backgroundColor = this._backgroundColor; + this.viewController = this._ios = controller; + } + return this._ios.view; } disposeNativeView() { @@ -488,7 +494,7 @@ export class Page extends PageBase { const insets = this.getSafeAreaInsets(); - if (!__VISIONOS__ && SDK_VERSION <= 10) { + if (!__VISIONOS__ && SDK_VERSION <= 10 && this.viewController) { // iOS 10 and below don't have safe area insets API, // there we need only the top inset on the Page insets.top = layout.round(layout.toDevicePixels(this.viewController.view.safeAreaLayoutGuide.layoutFrame.origin.y)); diff --git a/packages/core/ui/tab-view/index.ios.ts b/packages/core/ui/tab-view/index.ios.ts index 7680586c4e..ffbb5ae01e 100644 --- a/packages/core/ui/tab-view/index.ios.ts +++ b/packages/core/ui/tab-view/index.ios.ts @@ -153,7 +153,7 @@ class UINavigationControllerDelegateImpl extends NSObject implements UINavigatio if (owner) { // If viewController is one of our tab item controllers, then "< More" will be visible shortly. // Otherwise viewController is the UIMoreListController which shows the list of all tabs beyond the 4th tab. - const backToMoreWillBeVisible = owner._ios.viewControllers.containsObject(viewController); + const backToMoreWillBeVisible = owner.ios.viewControllers.containsObject(viewController); owner._handleTwoNavigationBars(backToMoreWillBeVisible); } } @@ -273,16 +273,24 @@ export class TabViewItem extends TabViewItemBase { export class TabView extends TabViewBase { public viewController: UITabBarControllerImpl; public items: TabViewItem[]; - public _ios: UITabBarControllerImpl; + private _delegate: UITabBarControllerDelegateImpl; private _moreNavigationControllerDelegate: UINavigationControllerDelegateImpl; private _iconsCache = {}; + private _ios: UITabBarControllerImpl; + private _actionBarHiddenByTabView: boolean; constructor() { super(); - this.viewController = this._ios = UITabBarControllerImpl.initWithOwner(new WeakRef(this)); - this.nativeViewProtected = this._ios.view; + } + + createNativeView() { + // View controller can be disposed during view disposal, so make sure to create a new one if not defined + if (!this._ios) { + this.viewController = this._ios = UITabBarControllerImpl.initWithOwner(new WeakRef(this)); + } + return this._ios.view; } initNativeView() { @@ -294,6 +302,8 @@ export class TabView extends TabViewBase { disposeNativeView() { this._delegate = null; this._moreNavigationControllerDelegate = null; + this.viewController = null; + this._ios = null; super.disposeNativeView(); } @@ -307,12 +317,19 @@ export class TabView extends TabViewBase { selectedView._pushInFrameStackRecursive(); } - this._ios.delegate = this._delegate; + if (this._ios) { + this._ios.delegate = this._delegate; + } } public onUnloaded() { - this._ios.delegate = null; - this._ios.moreNavigationController.delegate = null; + if (this._ios) { + this._ios.delegate = null; + + if (this._ios.moreNavigationController) { + this._ios.moreNavigationController.delegate = null; + } + } super.onUnloaded(); } @@ -371,7 +388,7 @@ export class TabView extends TabViewBase { if (Trace.isEnabled()) { Trace.write('TabView._onViewControllerShown(' + viewController + ');', Trace.categories.Debug); } - if (this._ios.viewControllers && this._ios.viewControllers.containsObject(viewController)) { + if (this._ios?.viewControllers && this._ios.viewControllers.containsObject(viewController)) { this.selectedIndex = this._ios.viewControllers.indexOfObject(viewController); } else { if (Trace.isEnabled()) { @@ -380,7 +397,6 @@ export class TabView extends TabViewBase { } } - private _actionBarHiddenByTabView: boolean; public _handleTwoNavigationBars(backToMoreWillBeVisible: boolean) { if (Trace.isEnabled()) { Trace.write(`TabView._handleTwoNavigationBars(backToMoreWillBeVisible: ${backToMoreWillBeVisible})`, Trace.categories.Debug); @@ -448,7 +464,6 @@ export class TabView extends TabViewBase { const length = items ? items.length : 0; if (length === 0) { this._ios.viewControllers = null; - return; } From f200d810696e0e78b427c181f497ec0d0863bcf6 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Thu, 6 Nov 2025 14:49:52 +0200 Subject: [PATCH 2/4] ref: Tab more navigation improvements --- packages/core/ui/tab-view/index.ios.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/core/ui/tab-view/index.ios.ts b/packages/core/ui/tab-view/index.ios.ts index ffbb5ae01e..f4187b3d2d 100644 --- a/packages/core/ui/tab-view/index.ios.ts +++ b/packages/core/ui/tab-view/index.ios.ts @@ -126,7 +126,7 @@ class UITabBarControllerDelegateImpl extends NSObject implements UITabBarControl const owner = this._owner?.deref(); if (owner) { - owner._onViewControllerShown(viewController); + owner._onViewControllerShown(tabBarController, viewController); } } } @@ -153,7 +153,7 @@ class UINavigationControllerDelegateImpl extends NSObject implements UINavigatio if (owner) { // If viewController is one of our tab item controllers, then "< More" will be visible shortly. // Otherwise viewController is the UIMoreListController which shows the list of all tabs beyond the 4th tab. - const backToMoreWillBeVisible = owner.ios.viewControllers.containsObject(viewController); + const backToMoreWillBeVisible = navigationController.tabBarController?.viewControllers?.containsObject?.(viewController); owner._handleTwoNavigationBars(backToMoreWillBeVisible); } } @@ -166,7 +166,7 @@ class UINavigationControllerDelegateImpl extends NSObject implements UINavigatio navigationController.navigationBar.topItem.rightBarButtonItem = null; const owner = this._owner?.deref(); if (owner) { - owner._onViewControllerShown(viewController); + owner._onViewControllerShown(navigationController.tabBarController, viewController); } } } @@ -383,13 +383,13 @@ export class TabView extends TabViewBase { this.setMeasuredDimension(widthAndState, heightAndState); } - public _onViewControllerShown(viewController: UIViewController) { + public _onViewControllerShown(tabBarController: UITabBarController, viewController: UIViewController) { // This method could be called with the moreNavigationController or its list controller, so we have to check. if (Trace.isEnabled()) { Trace.write('TabView._onViewControllerShown(' + viewController + ');', Trace.categories.Debug); } - if (this._ios?.viewControllers && this._ios.viewControllers.containsObject(viewController)) { - this.selectedIndex = this._ios.viewControllers.indexOfObject(viewController); + if (tabBarController?.viewControllers && tabBarController.viewControllers.containsObject(viewController)) { + this.selectedIndex = tabBarController.viewControllers.indexOfObject(viewController); } else { if (Trace.isEnabled()) { Trace.write('TabView._onViewControllerShown: viewController is not one of our viewControllers', Trace.categories.Debug); @@ -403,7 +403,12 @@ export class TabView extends TabViewBase { } // The "< Back" and "< More" navigation bars should not be visible simultaneously. - const page = this.page || this._selectedView?.page || (this)._selectedView?.currentPage; + let page = this.page || this._selectedView?.page; + + if (!page && this._selectedView instanceof Frame) { + page = this._selectedView.currentPage; + } + if (!page || !page.frame) { return; } From 7f8aeaafa8ad48b4cd13eefaa14ec62a9a81d2b4 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 7 Nov 2025 00:20:58 +0200 Subject: [PATCH 3/4] chore: Added few checks as precautions --- packages/core/ui/page/index.ios.ts | 2 +- packages/core/ui/tab-view/index.ios.ts | 25 +++++++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/core/ui/page/index.ios.ts b/packages/core/ui/page/index.ios.ts index 2b5c46eb30..50c2a124c3 100644 --- a/packages/core/ui/page/index.ios.ts +++ b/packages/core/ui/page/index.ios.ts @@ -444,7 +444,7 @@ export class Page extends PageBase { public _updateStatusBarStyle(value?: string) { const frame = this.frame; - if (frame && value) { + if (frame?.ios && value) { const navigationController: UINavigationController = frame.ios.controller; const navigationBar = navigationController.navigationBar; diff --git a/packages/core/ui/tab-view/index.ios.ts b/packages/core/ui/tab-view/index.ios.ts index f4187b3d2d..e2642499c7 100644 --- a/packages/core/ui/tab-view/index.ios.ts +++ b/packages/core/ui/tab-view/index.ios.ts @@ -108,8 +108,7 @@ class UITabBarControllerDelegateImpl extends NSObject implements UITabBarControl const owner = this._owner?.deref(); if (owner) { // "< More" cannot be visible after clicking on the main tab bar buttons. - const backToMoreWillBeVisible = false; - owner._handleTwoNavigationBars(backToMoreWillBeVisible); + owner._handleTwoNavigationBars(false); } if (tabBarController.selectedViewController === viewController) { @@ -416,9 +415,14 @@ export class TabView extends TabViewBase { const actionBarVisible = page.frame._getNavBarVisible(page); if (backToMoreWillBeVisible && actionBarVisible) { - page.frame.ios._disableNavBarAnimation = true; - page.actionBarHidden = true; - page.frame.ios._disableNavBarAnimation = false; + if (page.frame.ios) { + page.frame.ios._disableNavBarAnimation = true; + page.actionBarHidden = true; + page.frame.ios._disableNavBarAnimation = false; + } else { + page.actionBarHidden = true; + } + this._actionBarHiddenByTabView = true; if (Trace.isEnabled()) { Trace.write(`TabView hid action bar`, Trace.categories.Debug); @@ -428,9 +432,14 @@ export class TabView extends TabViewBase { } if (!backToMoreWillBeVisible && this._actionBarHiddenByTabView) { - page.frame.ios._disableNavBarAnimation = true; - page.actionBarHidden = false; - page.frame.ios._disableNavBarAnimation = false; + if (page.frame.ios) { + page.frame.ios._disableNavBarAnimation = true; + page.actionBarHidden = false; + page.frame.ios._disableNavBarAnimation = false; + } else { + page.actionBarHidden = false; + } + this._actionBarHiddenByTabView = undefined; if (Trace.isEnabled()) { Trace.write(`TabView restored action bar`, Trace.categories.Debug); From 5ed501c2013044b1637350de3ea03152d834ef08 Mon Sep 17 00:00:00 2001 From: Dimitris-Rafail Katsampas Date: Fri, 7 Nov 2025 00:26:47 +0200 Subject: [PATCH 4/4] chore: Removed unneeded optional chaining Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/core/ui/tab-view/index.ios.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/ui/tab-view/index.ios.ts b/packages/core/ui/tab-view/index.ios.ts index e2642499c7..62f1b2b599 100644 --- a/packages/core/ui/tab-view/index.ios.ts +++ b/packages/core/ui/tab-view/index.ios.ts @@ -152,7 +152,7 @@ class UINavigationControllerDelegateImpl extends NSObject implements UINavigatio if (owner) { // If viewController is one of our tab item controllers, then "< More" will be visible shortly. // Otherwise viewController is the UIMoreListController which shows the list of all tabs beyond the 4th tab. - const backToMoreWillBeVisible = navigationController.tabBarController?.viewControllers?.containsObject?.(viewController); + const backToMoreWillBeVisible = navigationController.tabBarController?.viewControllers?.containsObject(viewController); owner._handleTwoNavigationBars(backToMoreWillBeVisible); } }