import { TabContentItem } from '@nativescript-community/ui-material-core/tab-navigation-base/tab-content-item' ; import { TabNavigationBase , getIconSpecSize , itemsProperty , selectedIndexProperty , tabStripProperty } from '@nativescript-community/ui-material-core/tab-navigation-base/tab-navigation-base' ; import { TabStrip } from '@nativescript-community/ui-material-core/tab-navigation-base/tab-strip' ; import { TabStripItem } from '@nativescript-community/ui-material-core/tab-navigation-base/tab-strip-item' ; import { CSSType , Color , CoreTypes , Device , Font , Frame , IOSHelper , ImageSource , Property , Utils , View , booleanConverter } from '@nativescript/core' ; import { getTransformedText } from '@nativescript/core/ui/text-base' ; import { iOSNativeHelper } from '@nativescript/core/utils' ; export { TabContentItem , TabStrip , TabStripItem } ; const maxTabsCount = 5 ; const majorVersion = iOSNativeHelper . MajorVersion ; const isPhone = Device . deviceType === 'Phone' ; @ NativeClass class UITabBarControllerImpl extends UITabBarController { private _owner : WeakRef < BottomNavigation > ; public static initWithOwner ( owner ) { const handler = UITabBarControllerImpl . alloc ( ) . init ( ) as UITabBarControllerImpl ; handler . _owner = owner ; return handler ; } public viewWillAppear ( animated ) { super . viewWillAppear ( animated ) ; const owner = this . _owner . get ( ) ; if ( ! owner ) { return ; } this . extendedLayoutIncludesOpaqueBars = true ; IOSHelper . updateAutoAdjustScrollInsets ( this , owner ) ; if ( ! owner . parent ) { owner . callLoaded ( ) ; } } public viewDidDisappear ( animated ) { super . viewDidDisappear ( animated ) ; const owner = this . _owner ?. get ( ) ; if ( owner && ! owner . parent && owner . isLoaded && ! this . presentedViewController ) { owner . callUnloaded ( ) ; } } public viewWillTransitionToSizeWithTransitionCoordinator ( size , coordinator ) { super . viewWillTransitionToSizeWithTransitionCoordinator ( size , coordinator ) ; coordinator . animateAlongsideTransitionCompletion ( ( ) => { const owner = this . _owner . get ( ) ; if ( owner && owner . tabStrip && owner . tabStrip . items ) { const tabStrip = owner . tabStrip ; tabStrip . items . forEach ( ( tabStripItem ) => { updateBackgroundPositions ( tabStrip , tabStripItem ) ; const index = tabStripItem . _index ; const tabBarItemController = this . viewControllers [ index ] ; updateTitleAndIconPositions ( tabStripItem , tabBarItemController . tabBarItem , tabBarItemController ) ; } ) ; } } , null ) ; } public traitCollectionDidChange ( previousTraitCollection ) { super . traitCollectionDidChange ( previousTraitCollection ) ; if ( majorVersion >= 13 ) { const owner = this . _owner . get ( ) ; if ( owner && this . traitCollection . hasDifferentColorAppearanceComparedToTraitCollection && this . traitCollection . hasDifferentColorAppearanceComparedToTraitCollection ( previousTraitCollection ) ) { owner . notify ( { eventName : IOSHelper . traitCollectionColorAppearanceChangedEvent , object : owner } ) ; } } } } @ NativeClass class UITabBarControllerDelegateImpl extends NSObject implements UITabBarControllerDelegate { public static ObjCProtocols = [ UITabBarControllerDelegate ] ; private _owner : WeakRef < BottomNavigation > ; public static initWithOwner ( owner ) { const delegate = UITabBarControllerDelegateImpl . alloc ( ) . init ( ) as UITabBarControllerDelegateImpl ; delegate . _owner = owner ; return delegate ; } public tabBarControllerShouldSelectViewController ( tabBarController , viewController ) { const owner = this . _owner . get ( ) ; if ( owner ) { const backToMoreWillBeVisible = false ; owner . _handleTwoNavigationBars ( backToMoreWillBeVisible ) ; if ( tabBarController . viewControllers ) { const position = tabBarController . viewControllers . indexOfObject ( viewController ) ; if ( position !== NSNotFound ) { const tabStrip = owner . tabStrip ; const tabStripItems = tabStrip && tabStrip . items ; if ( tabStripItems && tabStripItems [ position ] ) { tabStripItems [ position ] . _emit ( TabStripItem . tapEvent ) ; tabStrip . notify ( { eventName : TabStrip . itemTapEvent , object : tabStrip , index : position } ) ; } } } } if ( ( tabBarController as any ) . selectedViewController === viewController ) { return false ; } ( tabBarController as any ) . _willSelectViewController = viewController ; return true ; } public tabBarControllerDidSelectViewController ( tabBarController , viewController ) { const owner = this . _owner . get ( ) ; if ( owner ) { owner . _onViewControllerShown ( viewController ) ; } ( tabBarController as any ) . _willSelectViewController = undefined ; } } @ NativeClass class UINavigationControllerDelegateImpl extends NSObject implements UINavigationControllerDelegate { public static ObjCProtocols = [ UINavigationControllerDelegate ] ; private _owner : WeakRef < BottomNavigation > ; public static initWithOwner ( owner ) { const delegate = UINavigationControllerDelegateImpl . alloc ( ) . init ( ) as UINavigationControllerDelegateImpl ; delegate . _owner = owner ; return delegate ; } navigationControllerWillShowViewControllerAnimated ( navigationController , viewController , animated ) { const owner = this . _owner . get ( ) ; if ( owner ) { const backToMoreWillBeVisible = owner . _ios . viewControllers . containsObject ( viewController ) ; owner . _handleTwoNavigationBars ( backToMoreWillBeVisible ) ; } } navigationControllerDidShowViewControllerAnimated ( navigationController , viewController , animated ) { navigationController . navigationBar . topItem . rightBarButtonItem = null ; const owner = this . _owner . get ( ) ; if ( owner ) { owner . _onViewControllerShown ( viewController ) ; } } } function updateBackgroundPositions ( tabStrip , tabStripItem ) { let bgView = ( tabStripItem as any ) . bgView ; const index = tabStripItem . _index ; const width = tabStrip . nativeView . frame . size . width / tabStrip . items . length ; const frame = CGRectMake ( width * index , 0 , width , tabStrip . nativeView . frame . size . width ) ; if ( ! bgView ) { bgView = UIView . alloc ( ) . initWithFrame ( frame ) ; tabStrip . nativeView . insertSubviewAtIndex ( bgView , 0 ) ; ( tabStripItem as any ) . bgView = bgView ; } else { bgView . frame = frame ; } const backgroundColor = tabStripItem . style . backgroundColor ; if ( backgroundColor ) { bgView . backgroundColor = backgroundColor instanceof Color ? backgroundColor . ios : backgroundColor ; } else { bgView . backgroundColor = new Color ( '#fff' ) . ios ; } } function updateTitleAndIconPositions ( tabStripItem , tabBarItem , controller ) { if ( ! tabStripItem || ! tabBarItem ) { return ; } const orientation = controller . interfaceOrientation ; const isPortrait = orientation !== UIInterfaceOrientation . LandscapeLeft && orientation !== UIInterfaceOrientation . LandscapeRight ; const isIconAboveTitle = majorVersion < 11 || ( isPhone && isPortrait ) ; if ( ! tabStripItem . iconSource ) { if ( isIconAboveTitle ) { tabBarItem . titlePositionAdjustment = { horizontal : 0 , vertical : - 20 } ; } else { tabBarItem . titlePositionAdjustment = { horizontal : 0 , vertical : 0 } ; } } if ( ! tabStripItem . title ) { if ( isIconAboveTitle ) { tabBarItem . imageInsets = new UIEdgeInsets ( { top : 6 , left : 0 , bottom : - 6 , right : 0 } ) ; } else { tabBarItem . imageInsets = new UIEdgeInsets ( { top : 0 , left : 0 , bottom : 0 , right : 0 } ) ; } } } export const iosCustomPositioningProperty = new Property < BottomNavigation , boolean > ( { name : 'iosCustomPositioning' , defaultValue : false , valueConverter : booleanConverter } ) ; @ CSSType ( 'BottomNavigation' ) export class BottomNavigation extends TabNavigationBase { public viewController : UITabBarControllerImpl ; public items : TabContentItem [ ] ; public _ios : UITabBarControllerImpl ; private _delegate : UITabBarControllerDelegateImpl ; private _moreNavigationControllerDelegate : UINavigationControllerDelegateImpl ; private _iconsCache = { } ; private _selectedItemColor : Color ; private _unSelectedItemColor : Color ; public iosCustomPositioning : boolean ; constructor ( ) { super ( ) ; this . viewController = this . _ios = UITabBarControllerImpl . initWithOwner ( new WeakRef ( this ) ) ; this . nativeViewProtected = this . _ios . view ; } initNativeView ( ) { super . initNativeView ( ) ; this . _delegate = UITabBarControllerDelegateImpl . initWithOwner ( new WeakRef ( this ) ) ; this . _moreNavigationControllerDelegate = UINavigationControllerDelegateImpl . initWithOwner ( new WeakRef ( this ) ) ; if ( ! this . tabStrip ) { this . viewController . tabBar . hidden = true ; } } disposeNativeView ( ) { this . _delegate = null ; this . _moreNavigationControllerDelegate = null ; super . disposeNativeView ( ) ; } public onLoaded ( ) { super . onLoaded ( ) ; this . setViewControllers ( this . items ) ; const selectedIndex = this . selectedIndex ; const selectedView = this . items && this . items [ selectedIndex ] && this . items [ selectedIndex ] . content ; if ( selectedView instanceof Frame ) { selectedView . _pushInFrameStackRecursive ( ) ; } this . _ios . delegate = this . _delegate ; this . onSelectedIndexChanged ( selectedIndex , selectedIndex ) ; } public onUnloaded ( ) { this . _ios . delegate = null ; this . _ios . moreNavigationController . delegate = null ; super . onUnloaded ( ) ; this . items . forEach ( ( item , i ) => { item . unloadView ( item . content ) ; } ) ; } get ios ( ) : UITabBarController { return this . _ios ; } public layoutNativeView ( left , top , right , bottom ) { if ( this . iosCustomPositioning ) { super . layoutNativeView ( left , top , right , bottom ) ; } } public _setNativeViewFrame ( nativeView , frame ) { if ( this . iosCustomPositioning ) { super . _setNativeViewFrame ( nativeView , frame ) ; } } public onSelectedIndexChanged ( oldIndex , newIndex ) { const items = this . items ; if ( ! items ) { return ; } if ( this . unloadOnTabChange ) { const oldItem = items [ oldIndex ] ; if ( oldItem ) { oldItem . canBeLoaded = false ; oldItem . unloadView ( oldItem . content ) ; } } const newItem = items [ newIndex ] ; if ( newItem && this . isLoaded ) { const selectedView = items [ newIndex ] . content ; if ( selectedView instanceof Frame ) { selectedView . _pushInFrameStackRecursive ( ) ; } newItem . canBeLoaded = true ; newItem . loadView ( newItem . content ) ; } const tabStripItems = this . tabStrip && this . tabStrip . items ; if ( tabStripItems ) { if ( tabStripItems [ newIndex ] ) { tabStripItems [ newIndex ] . _emit ( TabStripItem . selectEvent ) ; } if ( oldIndex !== newIndex && tabStripItems [ oldIndex ] ) { tabStripItems [ oldIndex ] . _emit ( TabStripItem . unselectEvent ) ; } } super . onSelectedIndexChanged ( oldIndex , newIndex ) ; } public getTabBarBackgroundColor ( ) { return this . _ios . tabBar . barTintColor ; } public setTabBarBackgroundColor ( value ) { this . _ios . tabBar . barTintColor = value instanceof Color ? value . ios : value ; this . updateAllItemsColors ( ) ; } public setTabBarItemTitle ( tabStripItem , value ) { tabStripItem . nativeView . title = value ; } public setTabBarItemBackgroundColor ( tabStripItem , value ) { if ( ! this . tabStrip || ! tabStripItem ) { return ; } updateBackgroundPositions ( this . tabStrip , tabStripItem ) ; } public setTabBarItemColor ( tabStripItem , value ) { this . setViewAttributes ( tabStripItem . nativeView , tabStripItem . label ) ; } private setItemColors ( ) { if ( this . _selectedItemColor ) { this . viewController . tabBar . selectedImageTintColor = this . _selectedItemColor . ios ; } if ( this . _unSelectedItemColor ) { this . viewController . tabBar . unselectedItemTintColor = this . _unSelectedItemColor . ios ; } } private setIconColor ( tabStripItem , forceReload = false ) { if ( forceReload || ( ! this . _unSelectedItemColor && ! this . _selectedItemColor ) ) { const tabStripColor = this . selectedIndex === tabStripItem . _index ? this . _selectedItemColor : this . _unSelectedItemColor ; const image = this . getIcon ( tabStripItem , tabStripColor ) ; tabStripItem . nativeView . image = image ; tabStripItem . nativeView . selectedImage = image ; } } public setTabBarIconColor ( tabStripItem , value ) { this . setIconColor ( tabStripItem ) ; } public setTabBarIconSource ( tabStripItem , value ) { this . updateItemColors ( tabStripItem ) ; } public setTabBarItemFontInternal ( tabStripItem , value ) { this . setViewAttributes ( tabStripItem . nativeView , tabStripItem . label ) ; } public setTabBarItemTextTransform ( tabStripItem , value ) { tabStripItem . nativeView . title = getTransformedText ( tabStripItem . label . text , value ) ; } public getTabBarHighlightColor ( ) { return this . _ios . tabBar . tintColor ; } public setTabBarHighlightColor ( value ) { this . _ios . tabBar . tintColor = value instanceof Color ? value . ios : value ; } public getTabBarSelectedItemColor ( ) { return this . _selectedItemColor ; } public setTabBarSelectedItemColor ( value ) { this . _selectedItemColor = value ; this . updateAllItemsColors ( ) ; } public getTabBarUnSelectedItemColor ( ) { return this . _unSelectedItemColor ; } public setTabBarUnSelectedItemColor ( value ) { this . _unSelectedItemColor = value ; this . updateAllItemsColors ( ) ; } public onMeasure ( widthMeasureSpec , heightMeasureSpec ) { const width = Utils . layout . getMeasureSpecSize ( widthMeasureSpec ) ; const widthMode = Utils . layout . getMeasureSpecMode ( widthMeasureSpec ) ; const height = Utils . layout . getMeasureSpecSize ( heightMeasureSpec ) ; const heightMode = Utils . layout . getMeasureSpecMode ( heightMeasureSpec ) ; const widthAndState = View . resolveSizeAndState ( width , width , widthMode , 0 ) ; const heightAndState = View . resolveSizeAndState ( height , height , heightMode , 0 ) ; this . setMeasuredDimension ( widthAndState , heightAndState ) ; } public _onViewControllerShown ( viewController ) { if ( this . _ios . viewControllers && this . _ios . viewControllers . containsObject ( viewController ) ) { this . selectedIndex = this . _ios . viewControllers . indexOfObject ( viewController ) ; } else { } } private _actionBarHiddenByTabView : boolean ; public _handleTwoNavigationBars ( backToMoreWillBeVisible ) { const page = this . page || ( this as any ) . _selectedView . page || ( this as any ) . _selectedView . currentPage ; if ( ! page || ! page . frame ) { return ; } const actionBarVisible = page . frame . _getNavBarVisible ( page ) ; if ( backToMoreWillBeVisible && actionBarVisible ) { page . frame . ios . _disableNavBarAnimation = true ; page . actionBarHidden = true ; page . frame . ios . _disableNavBarAnimation = false ; this . _actionBarHiddenByTabView = true ; return ; } if ( ! backToMoreWillBeVisible && this . _actionBarHiddenByTabView ) { page . frame . ios . _disableNavBarAnimation = true ; page . actionBarHidden = false ; page . frame . ios . _disableNavBarAnimation = false ; this . _actionBarHiddenByTabView = undefined ; return ; } } private getViewController ( item ) { let newController = item . content ? item . content . viewController : null ; if ( newController ) { ( item as any ) . setViewController ( newController , newController . view ) ; return newController ; } if ( item . content . ios instanceof UIViewController ) { newController = item . content . ios ; ( item as any ) . setViewController ( newController , newController . view ) ; } else if ( item . content . ios && item . content . ios . controller instanceof UIViewController ) { newController = item . content . ios . controller ; ( item as any ) . setViewController ( newController , newController . view ) ; } else { newController = IOSHelper . UILayoutViewController . initWithOwner ( new WeakRef ( item . content ) ) as UIViewController ; newController . view . addSubview ( item . content . nativeViewProtected ) ; item . content . viewController = newController ; ( item as any ) . setViewController ( newController , item . content . nativeViewProtected ) ; } return newController ; } private setViewControllers ( items ) { const length = items ? items . length : 0 ; if ( length === 0 ) { this . _ios . viewControllers = null ; return ; } items = items . slice ( 0 , maxTabsCount ) ; const controllers = NSMutableArray . alloc < UIViewController > ( ) . initWithCapacity ( length ) ; if ( this . tabStrip ) { this . tabStrip . setNativeView ( this . _ios . tabBar ) ; } items . forEach ( ( item , i ) => { const controller = this . getViewController ( item ) ; if ( this . tabStrip && this . tabStrip . items && this . tabStrip . items [ i ] ) { const tabStripItem = this . tabStrip . items [ i ] ; const tabBarItem = this . createTabBarItem ( tabStripItem , i ) ; updateTitleAndIconPositions ( tabStripItem , tabBarItem , controller ) ; this . setViewAttributes ( tabBarItem , tabStripItem . label ) ; controller . tabBarItem = tabBarItem ; tabStripItem . _index = i ; tabStripItem . setNativeView ( tabBarItem ) ; } controllers . addObject ( controller ) ; } ) ; this . setItemImages ( ) ; this . _ios . viewControllers = controllers ; this . _ios . customizableViewControllers = null ; this . _ios . moreNavigationController . delegate = this . _moreNavigationControllerDelegate ; } private setItemImages ( ) { if ( this . _selectedItemColor || this . _unSelectedItemColor ) { if ( this . tabStrip && this . tabStrip . items ) { this . tabStrip . items . forEach ( ( item ) => { if ( this . _unSelectedItemColor && item . nativeView ) { item . nativeView . image = this . getIcon ( item , this . _unSelectedItemColor ) ; item . nativeView . tintColor = this . _unSelectedItemColor ; } if ( this . _selectedItemColor && item . nativeView ) { item . nativeView . selectedImage = this . getIcon ( item , this . _selectedItemColor ) ; item . nativeView . tintColor = this . _selectedItemColor ; } } ) ; } } } public updateAllItemsColors ( ) { this . setItemColors ( ) ; if ( this . tabStrip && this . tabStrip . items ) { this . tabStrip . items . forEach ( ( tabStripItem ) => { this . updateItemColors ( tabStripItem ) ; } ) ; } } private updateItemColors ( tabStripItem ) { updateBackgroundPositions ( this . tabStrip , tabStripItem ) ; this . setIconColor ( tabStripItem , true ) ; } private createTabBarItem ( item , index ) { let image ; let title ; if ( item . isLoaded ) { image = this . getIcon ( item ) ; title = item . label . text ; const textTransform = item . label . style . textTransform ; if ( textTransform ) { title = getTransformedText ( title , textTransform ) ; } } return UITabBarItem . alloc ( ) . initWithTitleImageTag ( title , image , index ) ; } private getIconRenderingMode ( ) { switch ( this . tabStrip && this . tabStrip . iosIconRenderingMode ) { case 'alwaysOriginal' : return UIImageRenderingMode . AlwaysOriginal ; case 'alwaysTemplate' : return UIImageRenderingMode . AlwaysTemplate ; case 'automatic' : default : return UIImageRenderingMode . Automatic ; } } private getIcon ( tabStripItem , color ? ) { const iconSource = tabStripItem . image && tabStripItem . image . src ; if ( ! iconSource ) { return null ; } const target = tabStripItem . image ; const font = target . style . fontInternal || Font . default ; if ( ! color ) { color = target . style . color ; } const iconTag = [ iconSource , font . fontStyle , font . fontWeight , font . fontSize , font . fontFamily , color ] . join ( ';' ) ; let isFontIcon = false ; let image = this . _iconsCache [ iconTag ] ; if ( ! image ) { let is ; if ( Utils . isFontIconURI ( iconSource ) ) { isFontIcon = true ; const fontIconCode = iconSource . split ( '//' ) [ 1 ] ; is = ImageSource . fromFontIconCodeSync ( fontIconCode , font , color ) ; } else { is = ImageSource . fromFileOrResourceSync ( iconSource ) ; } if ( is && is . ios ) { image = is . ios ; if ( this . tabStrip && this . tabStrip . isIconSizeFixed ) { image = this . getFixedSizeIcon ( image ) ; } let renderingMode = UIImageRenderingMode . AlwaysOriginal ; if ( ! isFontIcon ) { renderingMode = this . getIconRenderingMode ( ) ; } const originalRenderedImage = image . imageWithRenderingMode ( renderingMode ) ; this . _iconsCache [ iconTag ] = originalRenderedImage ; image = originalRenderedImage ; } else { } } return image ; } private getFixedSizeIcon ( image ) { const inWidth = image . size . width ; const inHeight = image . size . height ; const iconSpecSize = getIconSpecSize ( { width : inWidth , height : inHeight } ) ; const widthPts = iconSpecSize . width ; const heightPts = iconSpecSize . height ; UIGraphicsBeginImageContextWithOptions ( { width : widthPts , height : heightPts } , false , Utils . layout . getDisplayDensity ( ) ) ; image . drawInRect ( CGRectMake ( 0 , 0 , widthPts , heightPts ) ) ; const resultImage = UIGraphicsGetImageFromCurrentImageContext ( ) ; UIGraphicsEndImageContext ( ) ; return resultImage ; } [ selectedIndexProperty . setNative ] ( value ) { if ( value > - 1 ) { ( this . _ios as any ) . _willSelectViewController = this . _ios . viewControllers [ value ] ; this . _ios . selectedIndex = value ; } } [ itemsProperty . getDefault ] ( ) { return null ; } [ itemsProperty . setNative ] ( value ) { if ( value ) { value . forEach ( ( item , i ) => { ( item as any ) . index = i ; } ) ; } this . setViewControllers ( value ) ; selectedIndexProperty . coerce ( this ) ; } [ tabStripProperty . getDefault ] ( ) { return null ; } [ tabStripProperty . setNative ] ( value ) { this . setViewControllers ( this . items ) ; selectedIndexProperty . coerce ( this ) ; } private setViewAttributes ( item , view ) { if ( ! view ) { return null ; } const defaultTabItemFontSize = 10 ; const tabItemFontSize = view . style . fontSize || defaultTabItemFontSize ; const font = ( view . style . fontInternal || Font . default ) . getUIFont ( UIFont . systemFontOfSize ( tabItemFontSize ) ) ; const tabItemTextColor = view . style . color ; const textColor = tabItemTextColor instanceof Color ? tabItemTextColor . ios : null ; const attributes = { [ NSFontAttributeName ] : font } ; if ( ! this . _selectedItemColor && ! this . _unSelectedItemColor ) { if ( textColor ) { attributes [ UITextAttributeTextColor ] = textColor ; attributes [ NSForegroundColorAttributeName ] = textColor ; } } else { this . viewController . tabBar . unselectedItemTintColor = this . _unSelectedItemColor && this . _unSelectedItemColor . ios ; this . viewController . tabBar . selectedImageTintColor = this . _selectedItemColor && this . _selectedItemColor . ios ; } item . setTitleTextAttributesForState ( attributes , UIControlState . Selected ) ; item . setTitleTextAttributesForState ( attributes , UIControlState . Normal ) ; if ( ! this . viewController . tabBar . barTintColor && attributes [ UITextAttributeTextColor ] && majorVersion > 9 ) { this . viewController . tabBar . unselectedItemTintColor = attributes [ UITextAttributeTextColor ] ; } } } iosCustomPositioningProperty . register ( BottomNavigation ) ;