@@ -252,22 +252,44 @@ export class TabViewItem extends TabViewItemBase {
252252 const index = parent . items . indexOf ( this ) ;
253253 const title = getTransformedText ( this . title , this . style . textTransform ) ;
254254
255- const tabBarItem = UITabBarItem . alloc ( ) . initWithTitleImageTag ( title , icon , index ) ;
256- updateTitleAndIconPositions ( this , tabBarItem , controller ) ;
257-
258- // There is no need to request title styles update here in newer versions as styling is handled by bar appearance instance
259- if ( ! __VISIONOS__ && SDK_VERSION < 15 ) {
260- // TODO: Repeating code. Make TabViewItemBase - ViewBase and move the colorProperty on tabViewItem.
261- // Delete the repeating code.
262- const states = getTitleAttributesForStates ( parent ) ;
263- applyStatesToItem ( tabBarItem , states ) ;
255+ if ( SDK_VERSION >= 18 ) {
256+ // iOS 18+: use UITab instead of UITabBarItem.
257+ // The UITab instances are created and managed at the TabView level,
258+ // so here we just update the corresponding tab for this controller.
259+ const identifier = `${ index } ` ;
260+ const tabController = parent . viewController as UITabBarController ;
261+ try {
262+ const tab = tabController . tabForIdentifier ( identifier ) ;
263+ if ( tab ) {
264+ tab . title = title ;
265+ tab . image = icon ;
266+ }
267+ } catch ( e ) {
268+ // Fallback: if tabForIdentifier is not available for some reason,
269+ // do not crash – rely on existing tab configuration.
270+ }
271+ } else {
272+ // iOS < 18: keep using UITabBarItem-based configuration.
273+ const tabBarItem = UITabBarItem . alloc ( ) . initWithTitleImageTag ( title , icon , index ) ;
274+ updateTitleAndIconPositions ( this , tabBarItem , controller ) ;
275+
276+ // There is no need to request title styles update here in newer versions as styling is handled by bar appearance instance
277+ if ( ! __VISIONOS__ && SDK_VERSION < 15 ) {
278+ // TODO: Repeating code. Make TabViewItemBase - ViewBase and move the colorProperty on tabViewItem.
279+ // Delete the repeating code.
280+ const states = getTitleAttributesForStates ( parent ) ;
281+ applyStatesToItem ( tabBarItem , states ) ;
282+ }
283+ controller . tabBarItem = tabBarItem ;
264284 }
265- controller . tabBarItem = tabBarItem ;
266285 }
267286 }
268287
269288 public _updateTitleAndIconPositions ( ) {
270- if ( ! this . __controller || ! this . __controller . tabBarItem ) {
289+ // UITab-based configuration (iOS 18+) does not expose the same per-item
290+ // title/icon positioning APIs as UITabBarItem, so we only adjust
291+ // positions when using the legacy UITabBarItem setup.
292+ if ( SDK_VERSION >= 18 || ! this . __controller || ! this . __controller . tabBarItem ) {
271293 return ;
272294 }
273295 updateTitleAndIconPositions ( this , this . __controller . tabBarItem , this . __controller ) ;
@@ -494,34 +516,78 @@ export class TabView extends TabViewBase {
494516 private setViewControllers ( items : TabViewItem [ ] ) {
495517 const length = items ? items . length : 0 ;
496518 if ( length === 0 ) {
497- this . _ios . viewControllers = null ;
519+ if ( SDK_VERSION >= 18 ) {
520+ // Clear tabs on iOS 18+ when there are no items.
521+ try {
522+ this . _ios . tabs = NSArray . arrayWithArray ( [ ] ) ;
523+ } catch ( e ) {
524+ // Fallback if tabs API is unavailable for some reason.
525+ this . _ios . viewControllers = null ;
526+ }
527+ } else {
528+ this . _ios . viewControllers = null ;
529+ }
498530 return ;
499531 }
500532
501- const controllers = NSMutableArray . alloc < UIViewController > ( ) . initWithCapacity ( length ) ;
502- const states = getTitleAttributesForStates ( this ) ;
533+ if ( SDK_VERSION >= 18 ) {
534+ // iOS 18+: build UITab instances and assign them to the controller.
535+ const tabs = [ ] ;
536+ const controllers = [ ] ;
537+ items . forEach ( ( item , i ) => {
538+ const controller = this . getViewController ( item ) ;
539+ controllers . push ( controller ) ;
540+ const icon = this . _getIcon ( item ) ;
541+ const title = item . title || '' ;
542+ const identifier = `${ i } ` ;
543+ let tab : UITab ;
544+ if ( item . role === 'search' ) {
545+ tab = UISearchTab . alloc ( ) . initWithTitleImageIdentifierViewControllerProvider ( title , icon , identifier , ( t ) => {
546+ return controller ;
547+ } ) ;
548+ } else {
549+ tab = UITab . alloc ( ) . initWithTitleImageIdentifierViewControllerProvider ( title , icon , identifier , ( t ) => {
550+ return controller ;
551+ } ) ;
552+ }
503553
504- items . forEach ( ( item , i ) => {
505- const controller = this . getViewController ( item ) ;
506- const icon = this . _getIcon ( item ) ;
507- const tabBarItem = UITabBarItem . alloc ( ) . initWithTitleImageTag ( item . title || '' , icon , i ) ;
508- updateTitleAndIconPositions ( item , tabBarItem , controller ) ;
554+ tabs . push ( tab ) ;
555+ ( < TabViewItemDefinition > item ) . canBeLoaded = true ;
556+ } ) ;
509557
510- if ( ! __VISIONOS__ && SDK_VERSION < 15 ) {
511- applyStatesToItem ( tabBarItem , states ) ;
512- }
558+ try {
559+ // Prefer animated setter when available.
560+ this . _ios . tabs = NSArray . arrayWithArray ( tabs ) ;
561+ } catch ( e ) { }
562+ this . _ios . viewControllers = NSArray . arrayWithArray ( controllers ) ;
563+ this . _ios . customizableViewControllers = null ;
564+ } else {
565+ // iOS < 18: keep using UITabBarItem-based configuration.
566+ const controllers = [ ] ;
567+ const states = getTitleAttributesForStates ( this ) ;
568+
569+ items . forEach ( ( item , i ) => {
570+ const controller = this . getViewController ( item ) ;
571+ const icon = this . _getIcon ( item ) ;
572+ const tabBarItem = UITabBarItem . alloc ( ) . initWithTitleImageTag ( item . title || '' , icon , i ) ;
573+ updateTitleAndIconPositions ( item , tabBarItem , controller ) ;
574+
575+ if ( ! __VISIONOS__ && SDK_VERSION < 15 ) {
576+ applyStatesToItem ( tabBarItem , states ) ;
577+ }
513578
514- controller . tabBarItem = tabBarItem ;
515- controllers . addObject ( controller ) ;
516- ( < TabViewItemDefinition > item ) . canBeLoaded = true ;
517- } ) ;
579+ controller . tabBarItem = tabBarItem ;
580+ controllers . push ( controller ) ;
581+ ( < TabViewItemDefinition > item ) . canBeLoaded = true ;
582+ } ) ;
518583
519- if ( SDK_VERSION >= 15 ) {
520- this . updateBarItemAppearance ( < UITabBar > this . _ios . tabBar , states ) ;
521- }
584+ if ( SDK_VERSION >= 15 ) {
585+ this . updateBarItemAppearance ( < UITabBar > this . _ios . tabBar , states ) ;
586+ }
522587
523- this . _ios . viewControllers = controllers ;
524- this . _ios . customizableViewControllers = null ;
588+ this . _ios . viewControllers = NSArray . arrayWithArray ( controllers ) ;
589+ this . _ios . customizableViewControllers = null ;
590+ }
525591
526592 // When we set this._ios.viewControllers, someone is clearing the moreNavigationController.delegate, so we have to reassign it each time here.
527593 this . _ios . moreNavigationController . delegate = this . _moreNavigationControllerDelegate ;
@@ -816,6 +882,13 @@ export class TabView extends TabViewBase {
816882
817883 const accessory = UITabAccessory . alloc ( ) . initWithContentView ( container ) ;
818884 setAccessory ( accessory ) ;
885+ // Work around UIKit occasionally caching accessory sizes too aggressively
886+ // by explicitly triggering a layout pass on the tab bar.
887+ const tabBar = this . _ios ?. tabBar ;
888+ if ( tabBar ) {
889+ tabBar . setNeedsLayout ( ) ;
890+ tabBar . layoutIfNeeded ( ) ;
891+ }
819892 // Keep references for later teardown
820893 this . _bottomAccessoryNsView = nsView ;
821894 }
@@ -825,11 +898,25 @@ export class TabView extends TabViewBase {
825898class NSTabAccessoryContainer extends UIView {
826899 _owner : WeakRef < View > ;
827900 static initWithOwner ( owner : WeakRef < View > ) : NSTabAccessoryContainer {
828- const v = NSTabAccessoryContainer . new ( ) as NSTabAccessoryContainer ;
901+ const v = NSTabAccessoryContainer . new ( ) as unknown as NSTabAccessoryContainer ;
829902 v . _owner = owner ;
830903 return v ;
831904 }
832905
906+ override traitCollectionDidChange ( previousTraitCollection : UITraitCollection ) {
907+ super . traitCollectionDidChange ( previousTraitCollection ) ;
908+ if ( ! previousTraitCollection ) {
909+ return ;
910+ }
911+ // When size classes change (e.g., compact regular),
912+ // ask UIKit to recompute this accessory's intrinsic size.
913+ if ( this . traitCollection ?. horizontalSizeClass !== previousTraitCollection . horizontalSizeClass ) {
914+ this . invalidateIntrinsicContentSize ( ) ;
915+ this . setNeedsLayout ( ) ;
916+ this . layoutIfNeeded ( ) ;
917+ }
918+ }
919+
833920 override layoutSubviews ( ) {
834921 super . layoutSubviews ( ) ;
835922 const owner = this . _owner ?. deref ( ) ;
0 commit comments