diff --git a/api-reports/NativeScript.api.md b/api-reports/NativeScript.api.md index 6bb4354fc..834f4fa39 100644 --- a/api-reports/NativeScript.api.md +++ b/api-reports/NativeScript.api.md @@ -1239,6 +1239,9 @@ export class iOSApplication { window: any /* UIWindow */; } +// @public +export type IOSTabBarItemsAlignment = "leading" | "justified" | "center" | "centerSelected"; + // @public export const isAndroid: boolean; @@ -2274,6 +2277,8 @@ export class Tabs extends TabNavigationBase { ios: any /* UITabBarController */; + iOSTabBarItemsAlignment: IOSTabBarItemsAlignment; + items: Array; offscreenTabLimit: number; diff --git a/e2e/ui-tests-app/app/tabs/main-page.ts b/e2e/ui-tests-app/app/tabs/main-page.ts index 0edd590c1..de21578a6 100644 --- a/e2e/ui-tests-app/app/tabs/main-page.ts +++ b/e2e/ui-tests-app/app/tabs/main-page.ts @@ -32,6 +32,7 @@ export function loadExamples() { examples.set("nested-layout", "tabs/nested-layout-page"); examples.set("nested-bottom-navigation", "tabs/nested-bottom-navigation-page"); examples.set("custom-tabstrip", "tabs/custom-tabstrip-page"); + examples.set("frame-in-tabs", "tabs/frame-in-tabs"); return examples; } diff --git a/e2e/ui-tests-app/app/tabs/tab-strip-items-page.xml b/e2e/ui-tests-app/app/tabs/tab-strip-items-page.xml index e98df7aa5..b27c15739 100644 --- a/e2e/ui-tests-app/app/tabs/tab-strip-items-page.xml +++ b/e2e/ui-tests-app/app/tabs/tab-strip-items-page.xml @@ -1,5 +1,5 @@ - + diff --git a/nativescript-core/platforms/ios/native-api-usage.json b/nativescript-core/platforms/ios/native-api-usage.json index 7e5dff0bd..9d144e8fd 100644 --- a/nativescript-core/platforms/ios/native-api-usage.json +++ b/nativescript-core/platforms/ios/native-api-usage.json @@ -14,6 +14,7 @@ "Foundation.*:*", "MaterialComponents.MDCTabBar:*", + "MaterialComponents.MDCTabBarIndicatorTemplate:*", "NativeScriptEmbedder:*", diff --git a/nativescript-core/platforms/ios/typings/objc!MaterialComponents.d.ts b/nativescript-core/platforms/ios/typings/objc!MaterialComponents.d.ts index 92ce4a5b4..71b6b1478 100644 --- a/nativescript-core/platforms/ios/typings/objc!MaterialComponents.d.ts +++ b/nativescript-core/platforms/ios/typings/objc!MaterialComponents.d.ts @@ -442,13 +442,17 @@ declare class MDCTabBar extends UIView implements UIBarPositioning { delegate: MDCTabBarDelegate; - displaysUppercaseTitles: boolean; + displaysUppercaseTitles: boolean; - inkColor: UIColor; + enableRippleBehavior: boolean; - itemAppearance: MDCTabBarItemAppearance; + inkColor: UIColor; - items: NSArray; + itemAppearance: MDCTabBarItemAppearance; + + items: NSArray; + + rippleColor: UIColor; selectedItem: UITabBarItem; diff --git a/nativescript-core/ui/bottom-navigation/bottom-navigation.android.ts b/nativescript-core/ui/bottom-navigation/bottom-navigation.android.ts index 18ebd2d02..b59f0414b 100644 --- a/nativescript-core/ui/bottom-navigation/bottom-navigation.android.ts +++ b/nativescript-core/ui/bottom-navigation/bottom-navigation.android.ts @@ -248,6 +248,7 @@ export class BottomNavigation extends TabNavigationBase { private _currentTransaction: androidx.fragment.app.FragmentTransaction; private _attachedToWindow = false; public _originalBackground: any; + private _textTransform: TextTransform = "none"; constructor() { super(); @@ -567,6 +568,18 @@ export class BottomNavigation extends TabNavigationBase { }); } + private getItemLabelTextTransform(tabStripItem: TabStripItem): TextTransform { + const nestedLabel = tabStripItem.label; + let textTransform: TextTransform = null; + if (nestedLabel && nestedLabel.style.textTransform !== "initial") { + textTransform = nestedLabel.style.textTransform; + } else if (tabStripItem.style.textTransform !== "initial") { + textTransform = tabStripItem.style.textTransform; + } + + return textTransform || this._textTransform; + } + private createTabItemSpec(tabStripItem: TabStripItem): org.nativescript.widgets.TabItemSpec { const tabItemSpec = new org.nativescript.widgets.TabItemSpec(); @@ -575,10 +588,8 @@ export class BottomNavigation extends TabNavigationBase { let title = titleLabel.text; // TEXT-TRANSFORM - const textTransform = titleLabel.style.textTransform; - if (textTransform) { - title = getTransformedText(title, textTransform); - } + const textTransform = this.getItemLabelTextTransform(tabStripItem); + title = getTransformedText(title, textTransform); tabItemSpec.title = title; // BACKGROUND-COLOR @@ -736,6 +747,24 @@ export class BottomNavigation extends TabNavigationBase { tabStripItem.nativeViewProtected.setText(title); } + public getTabBarTextTransform(): TextTransform { + return this._textTransform; + } + + public setTabBarTextTransform(value: TextTransform): void { + let items = this.tabStrip && this.tabStrip.items; + if (items) { + items.forEach((tabStripItem) => { + if (tabStripItem.label && tabStripItem.nativeViewProtected) { + const nestedLabel = tabStripItem.label; + const title = getTransformedText(nestedLabel.text, value); + tabStripItem.nativeViewProtected.setText(title); + } + }); + } + this._textTransform = value; + } + [selectedIndexProperty.setNative](value: number) { // const smoothScroll = false; diff --git a/nativescript-core/ui/bottom-navigation/bottom-navigation.ios.ts b/nativescript-core/ui/bottom-navigation/bottom-navigation.ios.ts index f80207282..9c0f4b703 100644 --- a/nativescript-core/ui/bottom-navigation/bottom-navigation.ios.ts +++ b/nativescript-core/ui/bottom-navigation/bottom-navigation.ios.ts @@ -71,20 +71,19 @@ class UITabBarControllerImpl extends UITabBarController { public viewWillTransitionToSizeWithTransitionCoordinator(size: CGSize, coordinator: UIViewControllerTransitionCoordinator): void { super.viewWillTransitionToSizeWithTransitionCoordinator(size, coordinator); - UIViewControllerTransitionCoordinator.prototype.animateAlongsideTransitionCompletion - .call(coordinator, () => { - const owner = this._owner.get(); - if (owner && owner.tabStrip && owner.tabStrip.items) { - const tabStrip = owner.tabStrip; - tabStrip.items.forEach(tabStripItem => { - updateBackgroundPositions(tabStrip, tabStripItem); + 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); + const index = tabStripItem._index; + const tabBarItemController = this.viewControllers[index]; + updateTitleAndIconPositions(tabStripItem, tabBarItemController.tabBarItem, tabBarItemController); + }); + } + }, null); } // Mind implementation for other controllers @@ -207,17 +206,14 @@ class UINavigationControllerDelegateImpl extends NSObject implements UINavigatio function updateBackgroundPositions(tabStrip: TabStrip, tabStripItem: TabStripItem) { let bgView = (tabStripItem).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) { - 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); bgView = UIView.alloc().initWithFrame(frame); tabStrip.nativeView.insertSubviewAtIndex(bgView, 0); (tabStripItem).bgView = bgView; } else { - 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); bgView.frame = frame; } @@ -376,8 +372,7 @@ export class BottomNavigation extends TabNavigationBase { } public setTabBarItemColor(tabStripItem: TabStripItem, value: UIColor | Color): void { - const states = getTitleAttributesForStates(tabStripItem.label); - applyStatesToItem(tabStripItem.nativeView, states, this.viewController.tabBar); + setViewTextAttributes(tabStripItem.nativeView, tabStripItem.label, this.viewController.tabBar); } public setTabBarIconColor(tabStripItem: TabStripItem, value: UIColor | Color): void { @@ -388,8 +383,7 @@ export class BottomNavigation extends TabNavigationBase { } public setTabBarItemFontInternal(tabStripItem: TabStripItem, value: Font): void { - const states = getTitleAttributesForStates(tabStripItem.label); - applyStatesToItem(tabStripItem.nativeView, states, this.viewController.tabBar); + setViewTextAttributes(tabStripItem.nativeView, tabStripItem.label, this.viewController.tabBar); } public setTabBarItemTextTransform(tabStripItem: TabStripItem, value: TextTransform): void { @@ -397,6 +391,15 @@ export class BottomNavigation extends TabNavigationBase { tabStripItem.nativeView.title = title; } + public getTabBarHighlightColor(): UIColor { + return this._ios.tabBar.tintColor; + } + + public setTabBarHighlightColor(value: UIColor | Color) { + const nativeColor = value instanceof Color ? value.ios : value; + this._ios.tabBar.tintColor = nativeColor; + } + public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { const width = layout.getMeasureSpecSize(widthMeasureSpec); const widthMode = layout.getMeasureSpecMode(widthMeasureSpec); @@ -518,8 +521,7 @@ export class BottomNavigation extends TabNavigationBase { const tabBarItem = this.createTabBarItem(tabStripItem, i); updateTitleAndIconPositions(tabStripItem, tabBarItem, controller); - const states = getTitleAttributesForStates(tabStripItem.label); - applyStatesToItem(tabBarItem, states, this.viewController.tabBar); + setViewTextAttributes(tabBarItem, tabStripItem.label, this.viewController.tabBar); controller.tabBarItem = tabBarItem; tabStripItem._index = i; @@ -688,51 +690,31 @@ export class BottomNavigation extends TabNavigationBase { } } -interface TabStates { - normalState?: any; - selectedState?: any; -} - -function getTitleAttributesForStates(view: View): TabStates { +function setViewTextAttributes(item: UITabBarItem, view: View, tabBar: UITabBar): any { if (!view) { return null; } - const result: TabStates = {}; const defaultTabItemFontSize = 10; const tabItemFontSize = view.style.fontSize || defaultTabItemFontSize; const font: UIFont = view.style.fontInternal.getUIFont(UIFont.systemFontOfSize(tabItemFontSize)); const tabItemTextColor = view.style.color; const textColor = tabItemTextColor instanceof Color ? tabItemTextColor.ios : null; - result.normalState = { [NSFontAttributeName]: font }; + let attributes: any = { [NSFontAttributeName]: font }; if (textColor) { - result.normalState[UITextAttributeTextColor] = textColor; + attributes[UITextAttributeTextColor] = textColor; + attributes[NSForegroundColorAttributeName] = textColor; } - const tabSelectedItemTextColor = view.style.color; - const selectedTextColor = tabSelectedItemTextColor instanceof Color ? tabSelectedItemTextColor.ios : null; - result.selectedState = { [NSFontAttributeName]: font }; - if (selectedTextColor) { - result.selectedState[UITextAttributeTextColor] = selectedTextColor; - } - - return result; -} - -function applyStatesToItem(item: UITabBarItem, states: TabStates, tabBar: UITabBar) { - if (!states) { - return; - } - - item.setTitleTextAttributesForState(states.normalState, UIControlState.Normal); - item.setTitleTextAttributesForState(states.selectedState, UIControlState.Selected); + item.setTitleTextAttributesForState(attributes, UIControlState.Selected); + item.setTitleTextAttributesForState(attributes, UIControlState.Normal); // there's a bug when setting the item color on ios 13 if there's no background set to the tabstrip // https://books.google.bg/books?id=99_BDwAAQBAJ&q=tabBar.unselectedItemTintColor // to fix the above issue we are applying the selected fix only for the case, when there is no background set // in that case we have the following known issue: // we will set the color to all unselected items, so you won't be able to set different colors for the different not selected items - if (!tabBar.barTintColor && states.normalState[UITextAttributeTextColor] && (majorVersion > 9)) { - tabBar.unselectedItemTintColor = states.normalState[UITextAttributeTextColor]; + if (!tabBar.barTintColor && attributes[UITextAttributeTextColor] && (majorVersion > 9)) { + tabBar.unselectedItemTintColor = attributes[UITextAttributeTextColor]; } -} +} \ No newline at end of file diff --git a/nativescript-core/ui/core/view/view-common.ts b/nativescript-core/ui/core/view/view-common.ts index d81d27ee2..8a8cea523 100644 --- a/nativescript-core/ui/core/view/view-common.ts +++ b/nativescript-core/ui/core/view/view-common.ts @@ -26,6 +26,7 @@ import { sanitizeModuleName } from "../../builder/module-name-sanitizer"; import { StyleScope } from "../../styling/style-scope"; import { LinearGradient } from "../../styling/linear-gradient"; import { BackgroundRepeat } from "../../styling/style-properties"; +import { TextTransform } from "../../text-base"; export * from "../../styling/style-properties"; export * from "../view-base"; @@ -718,6 +719,13 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { this.style.perspective = value; } + get textTransform(): TextTransform { + return this.style.textTransform; + } + set textTransform(value: TextTransform) { + this.style.textTransform = value; + } + get translateX(): dip { return this.style.translateX; } diff --git a/nativescript-core/ui/core/view/view.android.ts b/nativescript-core/ui/core/view/view.android.ts index 2388aa67c..bbfab0194 100644 --- a/nativescript-core/ui/core/view/view.android.ts +++ b/nativescript-core/ui/core/view/view.android.ts @@ -1116,7 +1116,7 @@ function createNativePercentLengthProperty(options: NativePercentLengthPropertyO setPercent = options.setPercent || percentNotSupported; options = null; } - if (length == "auto") { // tslint:disable-line + if (length == "auto" || !length) { // tslint:disable-line setPixels(this.nativeViewProtected, auto); } else if (typeof length === "number") { setPixels(this.nativeViewProtected, layout.round(layout.toDevicePixels(length))); diff --git a/nativescript-core/ui/core/view/view.ios.ts b/nativescript-core/ui/core/view/view.ios.ts index 18ab564a6..789d76577 100644 --- a/nativescript-core/ui/core/view/view.ios.ts +++ b/nativescript-core/ui/core/view/view.ios.ts @@ -180,7 +180,7 @@ export class View extends ViewCommon implements ViewDefinition { if (adjustedFrame) { nativeView.frame = adjustedFrame; } - + if (this._hasTransfrom) { // re-apply the transform after the frame is adjusted nativeView.layer.transform = transform; @@ -359,7 +359,7 @@ export class View extends ViewCommon implements ViewDefinition { if (this.rotateX || this.rotateY) { transform.m34 = -1 / perspective; } - + transform = CATransform3DTranslate(transform, this.translateX, this.translateY, 0); transform = iosNativeHelper.applyRotateTransform(transform, this.rotateX, this.rotateY, this.rotate); transform = CATransform3DScale(transform, scaleX, scaleY, 1); @@ -479,8 +479,7 @@ export class View extends ViewCommon implements ViewDefinition { parentController.presentViewControllerAnimatedCompletion(controller, animated, null); const transitionCoordinator = parentController.transitionCoordinator; if (transitionCoordinator) { - UIViewControllerTransitionCoordinator.prototype.animateAlongsideTransitionCompletion - .call(transitionCoordinator, null, () => this._raiseShownModallyEvent()); + transitionCoordinator.animateAlongsideTransitionCompletion(null, () => this._raiseShownModallyEvent()); } else { // Apparently iOS 9+ stops all transitions and animations upon application suspend and transitionCoordinator becomes null here in this case. // Since we are not waiting for any transition to complete, i.e. transitionCoordinator is null, we can directly raise our shownModally event. diff --git a/nativescript-core/ui/index.d.ts b/nativescript-core/ui/index.d.ts index 3ac104e6c..ac5934ff0 100644 --- a/nativescript-core/ui/index.d.ts +++ b/nativescript-core/ui/index.d.ts @@ -34,7 +34,7 @@ export { TabNavigationBase } from "./tab-navigation-base/tab-navigation-base"; export { TabStrip, TabStripItemEventData } from "./tab-navigation-base/tab-strip"; export { TabStripItem } from "./tab-navigation-base/tab-strip-item"; export { TabView, TabViewItem } from "./tab-view"; -export { Tabs } from "./tabs"; +export { Tabs, IOSTabBarItemsAlignment } from "./tabs"; export { TextBase } from "./text-base"; export { FormattedString } from "./text-base/formatted-string"; export { Span } from "./text-base/span"; diff --git a/nativescript-core/ui/tab-navigation-base/tab-navigation-base/tab-navigation-base.ts b/nativescript-core/ui/tab-navigation-base/tab-navigation-base/tab-navigation-base.ts index ee035bf1f..011d283ef 100644 --- a/nativescript-core/ui/tab-navigation-base/tab-navigation-base/tab-navigation-base.ts +++ b/nativescript-core/ui/tab-navigation-base/tab-navigation-base/tab-navigation-base.ts @@ -286,4 +286,4 @@ export const tabStripProperty = new Property({ target.onTabStripChanged(oldValue, newValue); } }); -tabStripProperty.register(TabNavigationBase); +tabStripProperty.register(TabNavigationBase); \ No newline at end of file diff --git a/nativescript-core/ui/tab-navigation-base/tab-strip-item/tab-strip-item.ts b/nativescript-core/ui/tab-navigation-base/tab-strip-item/tab-strip-item.ts index ead226c81..240cebec7 100644 --- a/nativescript-core/ui/tab-navigation-base/tab-strip-item/tab-strip-item.ts +++ b/nativescript-core/ui/tab-navigation-base/tab-strip-item/tab-strip-item.ts @@ -13,6 +13,7 @@ import { import { isIOS } from "../../../platform"; import { Image } from "../../image/image"; import { Label } from "../../label/label"; +import { textTransformProperty, TextTransform } from "../../text-base"; export * from "../../core/view"; export const traceCategory = "TabView"; @@ -236,6 +237,19 @@ export class TabStripItem extends View implements TabStripItemDefinition, AddChi return tabStripParent && tabStripParent.setTabBarItemBackgroundColor(this, value); } + [textTransformProperty.getDefault](): TextTransform { + const parent = this.parent; + const tabStripParent = parent && parent.parent; + + return tabStripParent && tabStripParent.getTabBarItemTextTransform(this); + } + [textTransformProperty.setNative](value: TextTransform) { + const parent = this.parent; + const tabStripParent = parent && parent.parent; + + return tabStripParent && tabStripParent.setTabBarItemTextTransform(this, value); + } + [backgroundInternalProperty.getDefault](): any { return null; } diff --git a/nativescript-core/ui/tab-view/tab-view.ios.ts b/nativescript-core/ui/tab-view/tab-view.ios.ts index 7d0cef33c..ec96eaaab 100644 --- a/nativescript-core/ui/tab-view/tab-view.ios.ts +++ b/nativescript-core/ui/tab-view/tab-view.ios.ts @@ -63,13 +63,12 @@ class UITabBarControllerImpl extends UITabBarController { public viewWillTransitionToSizeWithTransitionCoordinator(size: CGSize, coordinator: UIViewControllerTransitionCoordinator): void { super.viewWillTransitionToSizeWithTransitionCoordinator(size, coordinator); - UIViewControllerTransitionCoordinator.prototype.animateAlongsideTransitionCompletion - .call(coordinator, null, () => { - const owner = this._owner.get(); - if (owner && owner.items) { - owner.items.forEach(tabItem => tabItem._updateTitleAndIconPositions()); - } - }); + coordinator.animateAlongsideTransitionCompletion(null, () => { + const owner = this._owner.get(); + if (owner && owner.items) { + owner.items.forEach(tabItem => tabItem._updateTitleAndIconPositions()); + } + }); } // Mind implementation for other controllers diff --git a/nativescript-core/ui/tabs/tabs-common.ts b/nativescript-core/ui/tabs/tabs-common.ts index 71ff7f7d8..56f3f2ad9 100644 --- a/nativescript-core/ui/tabs/tabs-common.ts +++ b/nativescript-core/ui/tabs/tabs-common.ts @@ -22,6 +22,7 @@ export class TabsBase extends TabNavigationBase implements TabsDefinition { public swipeEnabled: boolean; public offscreenTabLimit: number; public tabsPosition: "top" | "bottom"; + public iOSTabBarItemsAlignment: IOSTabBarItemsAlignment; } // TODO: Add Unit tests @@ -39,3 +40,7 @@ offscreenTabLimitProperty.register(TabsBase); export const tabsPositionProperty = new Property({ name: "tabsPosition", defaultValue: "top" }); tabsPositionProperty.register(TabsBase); + +export type IOSTabBarItemsAlignment = "leading" | "justified" | "center" | "centerSelected"; +export const iOSTabBarItemsAlignmentProperty = new Property({ name: "iOSTabBarItemsAlignment", defaultValue: "justified" }); +iOSTabBarItemsAlignmentProperty.register(TabsBase); \ No newline at end of file diff --git a/nativescript-core/ui/tabs/tabs.android.ts b/nativescript-core/ui/tabs/tabs.android.ts index a2ab442df..afdbab527 100644 --- a/nativescript-core/ui/tabs/tabs.android.ts +++ b/nativescript-core/ui/tabs/tabs.android.ts @@ -373,6 +373,7 @@ export class Tabs extends TabsBase { private _pagerAdapter: androidx.viewpager.widget.PagerAdapter; private _androidViewId: number = -1; public _originalBackground: any; + private _textTransform: TextTransform = "uppercase"; constructor() { super(); @@ -637,6 +638,18 @@ export class Tabs extends TabsBase { }); } + private getItemLabelTextTransform(tabStripItem: TabStripItem): TextTransform { + const nestedLabel = tabStripItem.label; + let textTransform: TextTransform = null; + if (nestedLabel && nestedLabel.style.textTransform !== "initial") { + textTransform = nestedLabel.style.textTransform; + } else if (tabStripItem.style.textTransform !== "initial") { + textTransform = tabStripItem.style.textTransform; + } + + return textTransform || this._textTransform; + } + private createTabItemSpec(tabStripItem: TabStripItem): org.nativescript.widgets.TabItemSpec { const tabItemSpec = new org.nativescript.widgets.TabItemSpec(); @@ -645,10 +658,8 @@ export class Tabs extends TabsBase { let title = nestedLabel.text; // TEXT-TRANSFORM - const textTransform = nestedLabel.style.textTransform; - if (textTransform) { - title = getTransformedText(title, textTransform); - } + const textTransform = this.getItemLabelTextTransform(tabStripItem); + title = getTransformedText(title, textTransform); tabItemSpec.title = title; // BACKGROUND-COLOR @@ -817,7 +828,7 @@ export class Tabs extends TabsBase { const tabBarItem = this._tabsBar.getViewForItemAt(index); const imgView = tabBarItem.getChildAt(0); const drawable = this.getIcon(tabStripItem); - + imgView.setImageDrawable(drawable); } @@ -828,12 +839,34 @@ export class Tabs extends TabsBase { tabStripItem.nativeViewProtected.setTypeface(value.getAndroidTypeface()); } + public getTabBarItemTextTransform(tabStripItem: TabStripItem): TextTransform { + return this.getItemLabelTextTransform(tabStripItem); + } + public setTabBarItemTextTransform(tabStripItem: TabStripItem, value: TextTransform): void { const nestedLabel = tabStripItem.label; const title = getTransformedText(nestedLabel.text, value); tabStripItem.nativeViewProtected.setText(title); } + public getTabBarTextTransform(): TextTransform { + return this._textTransform; + } + + public setTabBarTextTransform(value: TextTransform): void { + let items = this.tabStrip && this.tabStrip.items; + if (items) { + items.forEach((tabStripItem) => { + if (tabStripItem.label && tabStripItem.nativeViewProtected) { + const nestedLabel = tabStripItem.label; + const title = getTransformedText(nestedLabel.text, value); + tabStripItem.nativeViewProtected.setText(title); + } + }); + } + this._textTransform = value; + } + [selectedIndexProperty.setNative](value: number) { const smoothScroll = true; diff --git a/nativescript-core/ui/tabs/tabs.d.ts b/nativescript-core/ui/tabs/tabs.d.ts index 17e740e91..feb397d9e 100644 --- a/nativescript-core/ui/tabs/tabs.d.ts +++ b/nativescript-core/ui/tabs/tabs.d.ts @@ -49,6 +49,16 @@ export class Tabs extends TabNavigationBase { */ tabsPosition: "top" | "bottom"; + /** + * Gets or set the MDCTabBarAlignment of the tab bar icons in iOS. Defaults to "justified" + * Valid values are: + * - leading + * - justified + * - center + * - centerSelected + */ + iOSTabBarItemsAlignment: IOSTabBarItemsAlignment; + /** * Gets the native [android widget](http://developer.android.com/reference/android/support/v4/view/ViewPager.html) that represents the user interface for this component. Valid only when running on Android OS. */ @@ -81,3 +91,12 @@ export class Tabs extends TabNavigationBase { export const itemsProperty: Property; export const tabStripProperty: Property export const selectedIndexProperty: Property; + +/** + * IOS Alignment of the Tabs TabStrip to use. + * - `leading` - tab items are aligned to the left + * - `justified` - tab strip is split equally to all the tab items + * - `center` - tabs items are aligned in the center + * - `centerSelected` - tab items move to make the selected tab in the center + */ +export type IOSTabBarItemsAlignment = "leading" | "justified" | "center" | "centerSelected"; diff --git a/nativescript-core/ui/tabs/tabs.ios.ts b/nativescript-core/ui/tabs/tabs.ios.ts index 9a59ce5b1..c8ce759d4 100644 --- a/nativescript-core/ui/tabs/tabs.ios.ts +++ b/nativescript-core/ui/tabs/tabs.ios.ts @@ -7,14 +7,15 @@ import { TextTransform, ViewBase } from "../text-base"; // Requires import { Color } from "../../color"; import { ImageSource } from "../../image-source"; +import { device } from "../../platform"; import { ios as iosUtils, isFontIconURI, layout } from "../../utils/utils"; import { ios as iosView, View } from "../core/view"; import { Frame } from "../frame"; import { Font } from "../styling/font"; import { - getIconSpecSize, itemsProperty, selectedIndexProperty, tabStripProperty + getIconSpecSize, itemsProperty, selectedIndexProperty, tabStripProperty, } from "../tab-navigation-base/tab-navigation-base"; -import { swipeEnabledProperty, TabsBase } from "./tabs-common"; +import { swipeEnabledProperty, TabsBase, IOSTabBarItemsAlignment, iOSTabBarItemsAlignmentProperty } from "./tabs-common"; // TODO // import { profile } from "../../profiling"; @@ -22,6 +23,7 @@ import { swipeEnabledProperty, TabsBase } from "./tabs-common"; export * from "./tabs-common"; const majorVersion = iosUtils.MajorVersion; +const isPhone = device.deviceType === "Phone"; // Equivalent to dispatch_async(dispatch_get_main_queue(...)) call const invokeOnRunLoop = (function () { @@ -74,6 +76,17 @@ class MDCTabBarDelegateImpl extends NSObject implements MDCTabBarDelegate { } } +class BackgroundIndicatorTemplate extends NSObject implements MDCTabBarIndicatorTemplate { + public static ObjCProtocols = [MDCTabBarIndicatorTemplate]; + + public indicatorAttributesForContext(context: MDCTabBarIndicatorContext): MDCTabBarIndicatorAttributes { + let attributes = new MDCTabBarIndicatorAttributes(); + attributes.path = UIBezierPath.bezierPathWithRect(context.bounds); + + return attributes; + } +} + class UIPageViewControllerImpl extends UIPageViewController { tabBar: MDCTabBar; scrollView: UIScrollView; @@ -114,7 +127,7 @@ class UIPageViewControllerImpl extends UIPageViewController { } tabBar.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleBottomMargin; - tabBar.alignment = MDCTabBarAlignment.Leading; + tabBar.alignment = MDCTabBarAlignment.Justified; tabBar.sizeToFit(); this.tabBar = tabBar; @@ -223,6 +236,24 @@ class UIPageViewControllerImpl extends UIPageViewController { } } } + + public viewWillTransitionToSizeWithTransitionCoordinator(size: CGSize, coordinator: UIViewControllerTransitionCoordinator): void { + 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, + this.tabBar.alignment !== MDCTabBarAlignment.Justified || (owner.selectedIndex !== tabStripItem._index) ? owner._defaultItemBackgroundColor : null); + + const index = tabStripItem._index; + const tabBarItemController = owner.viewControllers[index]; + updateTitleAndIconPositions(tabStripItem, tabBarItemController.tabBarItem, tabBarItemController); + }); + } + }, null); + } } class UIPageViewControllerDataSourceImpl extends NSObject implements UIPageViewControllerDataSource { @@ -355,167 +386,6 @@ class UIPageViewControllerDelegateImpl extends NSObject implements UIPageViewCon } } -// class UITabBarControllerImpl extends UITabBarController { - -// private _owner: WeakRef; - -// public static initWithOwner(owner: WeakRef): UITabBarControllerImpl { -// let handler = UITabBarControllerImpl.new(); -// handler._owner = owner; -// return handler; -// } - -// @profile -// public viewWillAppear(animated: boolean): void { -// super.viewWillAppear(animated); -// const owner = this._owner.get(); -// if (!owner) { -// return; -// } - -// // Unify translucent and opaque bars layout -// this.extendedLayoutIncludesOpaqueBars = true; - -// iosView.updateAutoAdjustScrollInsets(this, owner); - -// if (!owner.parent) { -// owner.callLoaded(); -// } -// } - -// @profile -// public viewDidDisappear(animated: boolean): void { -// super.viewDidDisappear(animated); -// const owner = this._owner.get(); -// if (owner && !owner.parent && owner.isLoaded && !this.presentedViewController) { -// owner.callUnloaded(); -// } -// } - -// public viewWillTransitionToSizeWithTransitionCoordinator(size: CGSize, coordinator: UIViewControllerTransitionCoordinator): void { -// super.viewWillTransitionToSizeWithTransitionCoordinator(size, coordinator); -// UIViewControllerTransitionCoordinator.prototype.animateAlongsideTransitionCompletion -// .call(coordinator, null, () => { -// const owner = this._owner.get(); -// if (owner && owner.items) { -// // owner.items.forEach(tabItem => tabItem._updateTitleAndIconPositions()); TODO: -// } -// }); -// } -// } - -// class UITabBarControllerDelegateImpl extends NSObject implements UITabBarControllerDelegate { -// public static ObjCProtocols = [UITabBarControllerDelegate]; - -// private _owner: WeakRef; - -// public static initWithOwner(owner: WeakRef): UITabBarControllerDelegateImpl { -// let delegate = UITabBarControllerDelegateImpl.new(); -// delegate._owner = owner; -// return delegate; -// } - -// public tabBarControllerShouldSelectViewController(tabBarController: UITabBarController, viewController: UIViewController): boolean { -// if (traceEnabled()) { -// traceWrite("TabView.delegate.SHOULD_select(" + tabBarController + ", " + viewController + ");", traceCategories.Debug); -// } - -// let owner = this._owner.get(); -// if (owner) { -// // "< More" cannot be visible after clicking on the main tab bar buttons. -// let backToMoreWillBeVisible = false; -// owner._handleTwoNavigationBars(backToMoreWillBeVisible); -// } - -// if ((tabBarController).selectedViewController === viewController) { -// return false; -// } - -// (tabBarController)._willSelectViewController = viewController; - -// return true; -// } - -// public tabBarControllerDidSelectViewController(tabBarController: UITabBarController, viewController: UIViewController): void { -// if (traceEnabled()) { -// traceWrite("TabView.delegate.DID_select(" + tabBarController + ", " + viewController + ");", traceCategories.Debug); -// } - -// const owner = this._owner.get(); -// if (owner) { -// owner._onViewControllerShown(viewController); -// } - -// (tabBarController)._willSelectViewController = undefined; -// } -// } - -// class UINavigationControllerDelegateImpl extends NSObject implements UINavigationControllerDelegate { -// public static ObjCProtocols = [UINavigationControllerDelegate]; - -// private _owner: WeakRef; - -// public static initWithOwner(owner: WeakRef): UINavigationControllerDelegateImpl { -// let delegate = UINavigationControllerDelegateImpl.new(); -// delegate._owner = owner; -// return delegate; -// } - -// navigationControllerWillShowViewControllerAnimated(navigationController: UINavigationController, viewController: UIViewController, animated: boolean): void { -// if (traceEnabled()) { -// traceWrite("TabView.moreNavigationController.WILL_show(" + navigationController + ", " + viewController + ", " + animated + ");", traceCategories.Debug); -// } - -// let owner = this._owner.get(); -// 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. -// let backToMoreWillBeVisible = owner._ios.viewControllers.containsObject(viewController); -// owner._handleTwoNavigationBars(backToMoreWillBeVisible); -// } -// } - -// navigationControllerDidShowViewControllerAnimated(navigationController: UINavigationController, viewController: UIViewController, animated: boolean): void { -// if (traceEnabled()) { -// traceWrite("TabView.moreNavigationController.DID_show(" + navigationController + ", " + viewController + ", " + animated + ");", traceCategories.Debug); -// } -// // We don't need Edit button in More screen. -// navigationController.navigationBar.topItem.rightBarButtonItem = null; -// let owner = this._owner.get(); -// if (owner) { -// owner._onViewControllerShown(viewController); -// } -// } -// } - -// function updateTitleAndIconPositions(tabStripItem: TabStripItem, tabBarItem: UITabBarItem, controller: UIViewController) { -// if (!tabStripItem || !tabBarItem) { -// return; -// } - -// // For iOS <11 icon is *always* above the text. -// // For iOS 11 icon is above the text *only* on phones in portrait mode. -// 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 }); -// } -// } -// } - function iterateIndexRange(index: number, eps: number, lastIndex: number, callback: (i) => void) { const rangeStart = Math.max(0, index - eps); const rangeEnd = Math.min(index + eps, lastIndex); @@ -524,6 +394,53 @@ function iterateIndexRange(index: number, eps: number, lastIndex: number, callba } } +function updateBackgroundPositions(tabStrip: TabStrip, tabStripItem: TabStripItem, color: UIColor = null) { + let bgView = (tabStripItem).bgView; + const index = tabStripItem._index; + let 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).bgView = bgView; + } + else { + bgView.frame = frame; + } + + const backgroundColor = tabStripItem.style.backgroundColor; + bgView.backgroundColor = color || (backgroundColor instanceof Color ? backgroundColor.ios : backgroundColor); + +} + +function updateTitleAndIconPositions(tabStripItem: TabStripItem, tabBarItem: UITabBarItem, controller: UIViewController) { + if (!tabStripItem || !tabBarItem) { + return; + } + + // For iOS <11 icon is *always* above the text. + // For iOS 11 icon is above the text *only* on phones in portrait mode. + 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 class Tabs extends TabsBase { public nativeViewProtected: UIView; public selectedIndex: number; @@ -542,11 +459,12 @@ export class Tabs extends TabsBase { private _delegate: UIPageViewControllerDelegateImpl; // private _moreNavigationControllerDelegate: UINavigationControllerDelegateImpl; private _iconsCache = {}; + private _backgroundIndicatorColor: UIColor; + public _defaultItemBackgroundColor: UIColor; constructor() { super(); - // this.viewController = this._ios = UIPageViewControllerImpl.initWithOwner(new WeakRef(this)); // .alloc().initWithTransitionStyleNavigationOrientationOptions(UIPageViewControllerTransitionStyle.Scroll, UIPageViewControllerNavigationOrientation.Horizontal, null); // UITabBarControllerImpl.initWithOwner(new WeakRef(this)); this.viewController = this._ios = UIPageViewControllerImpl.initWithOwner(new WeakRef(this)); //alloc().initWithTransitionStyleNavigationOrientationOptions(UIPageViewControllerTransitionStyle.Scroll, UIPageViewControllerNavigationOrientation.Horizontal, null);; } @@ -558,8 +476,6 @@ export class Tabs extends TabsBase { super.initNativeView(); this._dataSource = UIPageViewControllerDataSourceImpl.initWithOwner(new WeakRef(this)); this._delegate = UIPageViewControllerDelegateImpl.initWithOwner(new WeakRef(this)); - // this._delegate = UITabBarControllerDelegateImpl.initWithOwner(new WeakRef(this)); - // this._moreNavigationControllerDelegate = UINavigationControllerDelegateImpl.initWithOwner(new WeakRef(this)); } disposeNativeView() { @@ -567,7 +483,6 @@ export class Tabs extends TabsBase { this._delegate = null; this._ios.tabBarDelegate = null; this._ios.tabBar = null; - // this._moreNavigationControllerDelegate = null; super.disposeNativeView(); } @@ -576,23 +491,21 @@ export class Tabs extends TabsBase { 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(); + selectedView._pushInFrameStackRecursive(); } this._ios.dataSource = this._dataSource; this._ios.delegate = this._delegate; - - const tabStripItems = this.tabStrip ? this.tabStrip.items : null; - this.setTabStripItems(tabStripItems); } public onUnloaded() { this._ios.dataSource = null; this._ios.delegate = null; - // this._ios.moreNavigationController.delegate = null; super.onUnloaded(); } @@ -614,20 +527,33 @@ export class Tabs extends TabsBase { return; } - // const oldItem = items[oldIndex]; - // if (oldItem) { - // oldItem.unloadView(oldItem.view); - // } + 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].view; - // if (selectedView instanceof Frame) { - // selectedView._pushInFrameStackRecursive(); - // } + const newItem = items[newIndex]; + if (newItem && this.isLoaded) { + const selectedView = items[newIndex].content; + if (selectedView instanceof Frame) { + selectedView._pushInFrameStackRecursive(); + } - // newItem.loadView(newItem.view); - // } + 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 (tabStripItems[oldIndex]) { + tabStripItems[oldIndex]._emit(TabStripItem.unselectEvent); + } + } this._loadUnloadTabItems(newIndex); @@ -705,46 +631,6 @@ export class Tabs extends TabsBase { } } - // private _actionBarHiddenByTabView: boolean; - // public _handleTwoNavigationBars(backToMoreWillBeVisible: boolean) { - // // TODO - // // if (traceEnabled()) { - // // traceWrite(`TabView._handleTwoNavigationBars(backToMoreWillBeVisible: ${backToMoreWillBeVisible})`, traceCategories.Debug); - // // } - - // // The "< Back" and "< More" navigation bars should not be visible simultaneously. - // const page = this.page || this._selectedView.page || (this)._selectedView.currentPage; - // if (!page || !page.frame) { - // return; - // } - - // let actionBarVisible = page.frame._getNavBarVisible(page); - - // if (backToMoreWillBeVisible && actionBarVisible) { - // page.frame.ios._disableNavBarAnimation = true; - // page.actionBarHidden = true; - // page.frame.ios._disableNavBarAnimation = false; - // this._actionBarHiddenByTabView = true; - // // TODO - // // if (traceEnabled()) { - // // traceWrite(`TabView hid action bar`, traceCategories.Debug); - // // } - // return; - // } - - // if (!backToMoreWillBeVisible && this._actionBarHiddenByTabView) { - // page.frame.ios._disableNavBarAnimation = true; - // page.actionBarHidden = false; - // page.frame.ios._disableNavBarAnimation = false; - // this._actionBarHiddenByTabView = undefined; - // // TODO - // // if (traceEnabled()) { - // // traceWrite(`TabView restored action bar`, traceCategories.Debug); - // // } - // return; - // } - // } - private getViewController(item: TabContentItem): UIViewController { let newController: UIViewController = item.content ? item.content.viewController : null; @@ -789,115 +675,57 @@ export class Tabs extends TabsBase { private setViewControllers(items: TabContentItem[]) { const length = items ? items.length : 0; if (length === 0) { - // this._ios.setViewControllersDirectionAnimatedCompletion(null, null, false, null); + this.viewControllers = null; + return; } const viewControllers = []; + const tabBarItems = []; - items.forEach((item) => { + if (this.tabStrip) { + this.tabStrip.setNativeView(this._ios.tabBar); + } + + const tabStripItems = this.tabStrip && this.tabStrip.items; + if (tabStripItems) { + if (tabStripItems[this.selectedIndex]) { + tabStripItems[this.selectedIndex]._emit(TabStripItem.selectEvent); + } + } + + 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); + + setViewTextAttributes(this._ios.tabBar, tabStripItem.label, i === this.selectedIndex); + + controller.tabBarItem = tabBarItem; + tabStripItem._index = i; + tabBarItems.push(tabBarItem); + tabStripItem.setNativeView(tabBarItem); + } + + item.canBeLoaded = true; viewControllers.push(controller); }); this.viewControllers = viewControllers; - - // const controllers = NSMutableArray.alloc().initWithCapacity(length); - - // const selectedItem = items[this.selectedIndex]; - // const controller = this.getViewController(selectedItem); - // controllers.addObject(controller); - - // this._ios.setViewControllersDirectionAnimatedCompletion(controllers, UIPageViewControllerNavigationDirection.Forward, false, null); - - iterateIndexRange(this.selectedIndex, 1, this.items.length, (index) => { - (items[index]).canBeLoaded = true; - }); - - // (selectedItem).canBeLoaded = true; - - // const nextItem = items[this.selectedIndex + 1]; - // (nextItem).canBeLoaded = true; - - // const states = getTitleAttributesForStates(this); - - // items.forEach((item, i) => { - // const controller = this.getViewController(item); - - // let icon = null; - // let title = ""; - - // if (this.tabStrip && this.tabStrip.items && this.tabStrip.items[i]) { - // const tabStripItem = this.tabStrip.items[i]; - // icon = this._getIcon(tabStripItem.iconSource); - // title = tabStripItem.title; - - // const tabBarItem = UITabBarItem.alloc().initWithTitleImageTag((title || ""), icon, i); - // updateTitleAndIconPositions(tabStripItem, tabBarItem, controller); - - // applyStatesToItem(tabBarItem, states); - - // controller.tabBarItem = tabBarItem; - // } - - // controllers.addObject(controller); - // (item).canBeLoaded = true; - // }); - - // this._ios.viewControllers = controllers; - // this._ios.customizableViewControllers = null; - - // // When we set this._ios.viewControllers, someone is clearing the moreNavigationController.delegate, so we have to reassign it each time here. - // this._ios.moreNavigationController.delegate = this._moreNavigationControllerDelegate; - } - - private setTabStripItems(items: Array) { - if (!this.tabStrip || !items) { - return; - } - - const tabBarItems = []; - - items.forEach((tabStripItem: TabStripItem, i) => { - tabStripItem._index = i; - const tabBarItem = this.createTabBarItem(tabStripItem, i); - tabBarItems.push(tabBarItem); - tabStripItem.setNativeView(tabBarItem); - }); - this.tabBarItems = tabBarItems; if (this.viewController && this.viewController.tabBar) { this.viewController.tabBar.itemAppearance = this.getTabBarItemAppearance(); - this.viewController.tabBar.items = NSArray.arrayWithArray(tabBarItems); + this.viewController.tabBar.items = NSArray.arrayWithArray(this.tabBarItems); // TODO: investigate why this call is necessary to actually toggle item appearance this.viewController.tabBar.sizeToFit(); - this.tabStrip.setNativeView(this.viewController.tabBar); if (this.selectedIndex) { this.viewController.tabBar.setSelectedItemAnimated(this.tabBarItems[this.selectedIndex], false); } } - - // const length = items ? items.length : 0; - // if (length === 0) { - // this._tabLayout.setItems(null, null); - // return; - // } - - // const tabItems = new Array(); - // items.forEach((item: TabStripItem, i, arr) => { - // const tabItemSpec = createTabItemSpec(item); - // (item).index = i; - // (item).tabItemSpec = tabItemSpec; - // tabItems.push(tabItemSpec); - // }); - - // const tabLayout = this._tabLayout; - // tabLayout.setItems(tabItems, this._viewPager); - // items.forEach((item, i, arr) => { - // const tv = tabLayout.getTextViewForItemAt(i); - // item.setNativeView(tv); - // }); } private createTabBarItem(item: TabStripItem, index: number): UITabBarItem { @@ -924,9 +752,9 @@ export class Tabs extends TabsBase { private getTabBarItemAppearance(): MDCTabBarItemAppearance { let itemAppearance; - if (this.tabStrip._hasImage && this.tabStrip._hasTitle) { + if (this.tabStrip && this.tabStrip._hasImage && this.tabStrip._hasTitle) { itemAppearance = MDCTabBarItemAppearance.TitledImages; - } else if (this.tabStrip._hasImage) { + } else if (this.tabStrip && this.tabStrip._hasImage) { itemAppearance = MDCTabBarItemAppearance.Images; } else { itemAppearance = MDCTabBarItemAppearance.Titles; @@ -941,6 +769,8 @@ export class Tabs extends TabsBase { } private getIcon(tabStripItem: TabStripItem): UIImage { + // Image and Label children of TabStripItem + // take priority over its `iconSource` and `title` properties const iconSource = tabStripItem.image && tabStripItem.image.src; if (!iconSource) { return null; @@ -948,13 +778,15 @@ export class Tabs extends TabsBase { const target = tabStripItem.image; const font = target.style.fontInternal; - const color = tabStripItem.parent.style.color; + const color = target.style.color; const iconTag = [iconSource, font.fontStyle, font.fontWeight, font.fontSize, font.fontFamily, color].join(";"); + let isFontIcon = false; let image: UIImage = this._iconsCache[iconTag]; if (!image) { let is = new ImageSource; if (isFontIconURI(iconSource)) { + isFontIcon = true; const fontIconCode = iconSource.split("//")[1]; is = ImageSource.fromFontIconCodeSync(fontIconCode, font, color); } else { @@ -968,7 +800,11 @@ export class Tabs extends TabsBase { image = this.getFixedSizeIcon(image); } - const originalRenderedImage = image.imageWithRenderingMode(this.getIconRenderingMode()); + let renderingMode: UIImageRenderingMode = UIImageRenderingMode.AlwaysOriginal; + if (!isFontIcon) { + renderingMode = this.getIconRenderingMode(); + } + const originalRenderedImage = image.imageWithRenderingMode(renderingMode); this._iconsCache[iconTag] = originalRenderedImage; image = originalRenderedImage; } else { @@ -997,26 +833,6 @@ export class Tabs extends TabsBase { return resultImage; } - // private _updateIOSTabBarColorsAndFonts(): void { - // if (!this.tabStrip || !this.tabStrip.items || !this.tabStrip.items.length) { - // return; - // } - - // const tabBar = this.ios.tabBar; - // const states = getTitleAttributesForStates(this); - // for (let i = 0; i < tabBar.items.count; i++) { - // applyStatesToItem(tabBar.items[i], states); - // } - // } - - // TODO: Move this to TabStripItem - // [fontInternalProperty.getDefault](): Font { - // return null; - // } - // [fontInternalProperty.setNative](value: Font) { - // this._updateIOSTabBarColorsAndFonts(); - // } - public getTabBarBackgroundColor(): UIColor { return this._ios.tabBar.barTintColor; } @@ -1025,6 +841,69 @@ export class Tabs extends TabsBase { this._ios.tabBar.barTintColor = value instanceof Color ? value.ios : value; } + public setTabBarItemTitle(tabStripItem: TabStripItem, value: string): void { + tabStripItem.nativeView.title = value; + } + + private equalUIColor(first: UIColor, second: UIColor): boolean { + if (!first && !second) { + return true; + } + if (!first || !second) { + return false; + } + const firstComponents = CGColorGetComponents(first.CGColor); + const secondComponents = CGColorGetComponents(second.CGColor); + + return firstComponents[0] === secondComponents[0] + && firstComponents[1] === secondComponents[1] + && firstComponents[2] === secondComponents[2] + && firstComponents[3] === secondComponents[3]; + } + + private isSelectedAndHightlightedItem(tabStripItem: TabStripItem): boolean { + return (tabStripItem._index === this.selectedIndex && tabStripItem["_visualState"] === "highlighted"); + } + + public setTabBarItemBackgroundColor(tabStripItem: TabStripItem, value: UIColor | Color): void { + if (!this.tabStrip || !tabStripItem) { + return; + } + + let newColor = value instanceof Color ? value.ios : value; + const itemSelectedAndHighlighted = this.isSelectedAndHightlightedItem(tabStripItem); + + if (!this._defaultItemBackgroundColor && !itemSelectedAndHighlighted) { + this._defaultItemBackgroundColor = newColor; + } + + if (this.viewController.tabBar.alignment !== MDCTabBarAlignment.Justified && itemSelectedAndHighlighted + && !this.equalUIColor(this._defaultItemBackgroundColor, newColor)) { + if (!this._backgroundIndicatorColor) { + this._backgroundIndicatorColor = newColor; + this._ios.tabBar.selectionIndicatorTemplate = new BackgroundIndicatorTemplate(); + this._ios.tabBar.tintColor = newColor; + } + } else { + updateBackgroundPositions(this.tabStrip, tabStripItem, newColor); + } + } + + public setTabBarItemColor(tabStripItem: TabStripItem, value: UIColor | Color): void { + setViewTextAttributes(this._ios.tabBar, tabStripItem.label); + } + + public setTabBarIconColor(tabStripItem: TabStripItem, value: UIColor | Color): void { + const image = this.getIcon(tabStripItem); + + tabStripItem.nativeView.image = image; + tabStripItem.nativeView.selectedImage = image; + } + + public setTabBarItemFontInternal(tabStripItem: TabStripItem, value: Font): void { + setViewTextAttributes(this._ios.tabBar, tabStripItem.label); + } + public getTabBarFontInternal(): UIFont { return this._ios.tabBar.unselectedItemTitleFont; } @@ -1039,7 +918,15 @@ export class Tabs extends TabsBase { } public getTabBarTextTransform(): TextTransform { - return null; + switch (this._ios.tabBar.titleTextTransform) { + case MDCTabBarTextTransform.None: + return "none"; + case MDCTabBarTextTransform.Automatic: + return "initial"; + case MDCTabBarTextTransform.Uppercase: + default: + return "uppercase"; + } } public setTabBarTextTransform(value: TextTransform): void { @@ -1047,11 +934,13 @@ export class Tabs extends TabsBase { this._ios.tabBar.titleTextTransform = MDCTabBarTextTransform.None; } else if (value === "uppercase") { this._ios.tabBar.titleTextTransform = MDCTabBarTextTransform.Uppercase; + } else if (value === "initial") { + this._ios.tabBar.titleTextTransform = MDCTabBarTextTransform.Automatic; } } public getTabBarColor(): UIColor { - return this._ios.tabBar.titleColorForState(MDCTabBarItemState.Normal); + return this._ios.tabBar.titleColorForState(MDCTabBarItemState.Normal); } public setTabBarColor(value: UIColor | Color): void { @@ -1134,6 +1023,12 @@ export class Tabs extends TabsBase { return null; } [itemsProperty.setNative](value: TabContentItem[]) { + if (value) { + value.forEach((item: TabContentItem, i) => { + (item).index = i; + }); + } + this.setViewControllers(value); selectedIndexProperty.coerce(this); } @@ -1141,16 +1036,74 @@ export class Tabs extends TabsBase { [tabStripProperty.getDefault](): TabStrip { return null; } + [tabStripProperty.setNative](value: TabStrip) { - this.setTabStripItems(value.items); + this.setViewControllers(this.items); + selectedIndexProperty.coerce(this); } [swipeEnabledProperty.getDefault](): boolean { return true; } + [swipeEnabledProperty.setNative](value: boolean) { if (this.viewController && this.viewController.scrollView) { this.viewController.scrollView.scrollEnabled = value; } } + + [iOSTabBarItemsAlignmentProperty.getDefault](): IOSTabBarItemsAlignment { + if (!this.viewController || !this.viewController.tabBar) { + return "justified"; + } + + let alignment = this.viewController.tabBar.alignment.toString(); + + return (alignment.charAt(0).toLowerCase() + alignment.substring(1)); + } + + [iOSTabBarItemsAlignmentProperty.setNative](value: IOSTabBarItemsAlignment) { + if (!this.viewController || !this.viewController.tabBar) { + return; + } + + let alignment = MDCTabBarAlignment.Justified; + switch (value) { + case "leading": + alignment = MDCTabBarAlignment.Leading; + break; + case "center": + alignment = MDCTabBarAlignment.Center; + break; + case "centerSelected": + alignment = MDCTabBarAlignment.CenterSelected; + break; + } + + this.viewController.tabBar.alignment = alignment; + } } + +function setViewTextAttributes(tabBar: MDCTabBar, view: View, setSelected: boolean = false): any { + if (!view) { + return null; + } + + const defaultTabItemFontSize = 10; + const tabItemFontSize = view.style.fontSize || defaultTabItemFontSize; + const font: UIFont = view.style.fontInternal.getUIFont(UIFont.systemFontOfSize(tabItemFontSize)); + + tabBar.unselectedItemTitleFont = font; + tabBar.selectedItemTitleFont = font; + + const tabItemTextColor = view.style.color; + const textColor = tabItemTextColor instanceof Color ? tabItemTextColor.ios : null; + if (textColor) { + tabBar.setTitleColorForState(textColor, MDCTabBarItemState.Normal); + if (setSelected) { + tabBar.setTitleColorForState(textColor, MDCTabBarItemState.Selected); + } + } + + tabBar.inkColor = UIColor.clearColor; +} \ No newline at end of file