diff --git a/e2e/ui-tests-app/app/tabs/main-page.ts b/e2e/ui-tests-app/app/tabs/main-page.ts index 625fa25c69..33d8ab2c81 100644 --- a/e2e/ui-tests-app/app/tabs/main-page.ts +++ b/e2e/ui-tests-app/app/tabs/main-page.ts @@ -1,8 +1,9 @@ import { EventData } from "tns-core-modules/data/observable"; -import { SubMainPageViewModel } from "../sub-main-page-view-model"; import { WrapLayout } from "tns-core-modules/ui/layouts/wrap-layout"; import { Page } from "tns-core-modules/ui/page"; +import { SubMainPageViewModel } from "../sub-main-page-view-model"; + export function pageLoaded(args: EventData) { const page = args.object; const wrapLayout = page.getViewById("wrapLayoutWithExamples"); @@ -10,7 +11,7 @@ export function pageLoaded(args: EventData) { } export function loadExamples() { - const examples = new Map(); + const examples = new Map(); examples.set("tabs", "tabs/tabs-page"); examples.set("issue-5470", "tabs/issue-5470"); examples.set("background-color", "tabs/background-color-page"); @@ -18,8 +19,10 @@ export function loadExamples() { examples.set("icon-title-placement", "tabs/icon-title-placement"); examples.set("icon-change", "tabs/icon-change"); examples.set("swipe-enabled", "tabs/swipe-enabled"); + examples.set("strip-item", "tabs/tab-strip-item-page"); + examples.set("strip-items", "tabs/tab-strip-items-page"); examples.set("tabs-position", "tabs/tabs-position-page"); examples.set("tabs-binding", "tabs/tabs-binding-page"); - + return examples; } diff --git a/e2e/ui-tests-app/app/tabs/main-page.xml b/e2e/ui-tests-app/app/tabs/main-page.xml index 33306f0d02..79cc239910 100644 --- a/e2e/ui-tests-app/app/tabs/main-page.xml +++ b/e2e/ui-tests-app/app/tabs/main-page.xml @@ -3,4 +3,4 @@ - \ No newline at end of file + diff --git a/e2e/ui-tests-app/app/tabs/tab-strip-item-page.xml b/e2e/ui-tests-app/app/tabs/tab-strip-item-page.xml new file mode 100644 index 0000000000..e5f9c41ba4 --- /dev/null +++ b/e2e/ui-tests-app/app/tabs/tab-strip-item-page.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + 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 new file mode 100644 index 0000000000..323109806e --- /dev/null +++ b/e2e/ui-tests-app/app/tabs/tab-strip-items-page.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tns-core-modules/ui/tab-navigation-base/tab-strip-item/tab-strip-item.d.ts b/tns-core-modules/ui/tab-navigation-base/tab-strip-item/tab-strip-item.d.ts index 2506803fc9..7d82cb3820 100644 --- a/tns-core-modules/ui/tab-navigation-base/tab-strip-item/tab-strip-item.d.ts +++ b/tns-core-modules/ui/tab-navigation-base/tab-strip-item/tab-strip-item.d.ts @@ -4,6 +4,8 @@ */ /** */ import { View, EventData } from "../../core/view"; +import { Image } from "../../image/image"; +import { Label } from "../../label/label"; /** * Represents a tab strip entry. @@ -19,6 +21,16 @@ export class TabStripItem extends View { */ iconSource: string; + /** + * Gets or sets the label of the tab strip entry. + */ + label: Label; + + /** + * Gets or sets the image of the tab strip entry. + */ + image: Image; + /** * String value used when hooking to the tap event. */ diff --git a/tns-core-modules/ui/tab-navigation-base/tab-strip/tab-strip.d.ts b/tns-core-modules/ui/tab-navigation-base/tab-strip/tab-strip.d.ts index d6b5dc041b..70b5afe17e 100644 --- a/tns-core-modules/ui/tab-navigation-base/tab-strip/tab-strip.d.ts +++ b/tns-core-modules/ui/tab-navigation-base/tab-strip/tab-strip.d.ts @@ -21,6 +21,16 @@ export class TabStrip extends View { * Gets or sets the icon rendering mode on iOS */ iosIconRenderingMode: "automatic" | "alwaysOriginal" | "alwaysTemplate"; + + /** + * @private + */ + _hasImage: boolean; + + /** + * @private + */ + _hasTitle: boolean; } export const iosIconRenderingModeProperty: Property; diff --git a/tns-core-modules/ui/tab-navigation-base/tab-strip/tab-strip.ts b/tns-core-modules/ui/tab-navigation-base/tab-strip/tab-strip.ts index d0417dbd89..2ff16e8302 100644 --- a/tns-core-modules/ui/tab-navigation-base/tab-strip/tab-strip.ts +++ b/tns-core-modules/ui/tab-navigation-base/tab-strip/tab-strip.ts @@ -14,6 +14,8 @@ export const traceCategory = "TabView"; export class TabStrip extends View implements TabStripDefinition, AddChildFromBuilder, AddArrayFromBuilder { public items: TabStripItem[]; public iosIconRenderingMode: "automatic" | "alwaysOriginal" | "alwaysTemplate"; + public _hasImage: boolean; + public _hasTitle: boolean; public eachChild(callback: (child: ViewBase) => boolean) { const items = this.items; @@ -48,7 +50,7 @@ export class TabStrip extends View implements TabStripDefinition, AddChildFromBu } [backgroundColorProperty.setNative](value: Color) { const parent = this.parent; - + return parent && parent.setTabBarBackgroundColor(value); } diff --git a/tns-core-modules/ui/tabs/tabs.android.ts b/tns-core-modules/ui/tabs/tabs.android.ts index 7d4b1677e4..5aab66fe97 100644 --- a/tns-core-modules/ui/tabs/tabs.android.ts +++ b/tns-core-modules/ui/tabs/tabs.android.ts @@ -234,29 +234,34 @@ function initializeNativeClasses() { } function createTabItemSpec(item: TabStripItem): org.nativescript.widgets.TabItemSpec { - const result = new org.nativescript.widgets.TabItemSpec(); - result.title = item.title; - - if (item.iconSource) { - if (item.iconSource.indexOf(RESOURCE_PREFIX) === 0) { - result.iconId = ad.resources.getDrawableId(item.iconSource.substr(RESOURCE_PREFIX.length)); - if (result.iconId === 0) { + let iconSource; + const tabItemSpec = new org.nativescript.widgets.TabItemSpec(); + + // Image and Label children of TabStripItem + // take priority over its `iconSource` and `title` properties + iconSource = item.image ? item.image.src : item.iconSource; + tabItemSpec.title = item.label ? item.label.text : item.title; + + if (iconSource) { + if (iconSource.indexOf(RESOURCE_PREFIX) === 0) { + tabItemSpec.iconId = ad.resources.getDrawableId(iconSource.substr(RESOURCE_PREFIX.length)); + if (tabItemSpec.iconId === 0) { // TODO - // traceMissingIcon(item.iconSource); + // traceMissingIcon(iconSource); } } else { - const is = fromFileOrResource(item.iconSource); + const is = fromFileOrResource(iconSource); if (is) { // TODO: Make this native call that accepts string so that we don't load Bitmap in JS. - result.iconDrawable = new android.graphics.drawable.BitmapDrawable(application.android.context.getResources(), is.android); + tabItemSpec.iconDrawable = new android.graphics.drawable.BitmapDrawable(application.android.context.getResources(), is.android); } else { // TODO - // traceMissingIcon(item.iconSource); + // traceMissingIcon(iconSource); } } } - return result; + return tabItemSpec; } let defaultAccentColor: number = undefined; diff --git a/tns-core-modules/ui/tabs/tabs.d.ts b/tns-core-modules/ui/tabs/tabs.d.ts index 67cdd0b264..17e740e913 100644 --- a/tns-core-modules/ui/tabs/tabs.d.ts +++ b/tns-core-modules/ui/tabs/tabs.d.ts @@ -1,81 +1,83 @@ /** - * Contains the TabView class, which represents a standard content component with tabs. - * @module "ui/tab-view" + * Contains the Tabs class, which represents a tab navigation component. + * @module "ui/tabs" */ /** */ - import { Property, EventData } from "../core/view"; - import { TabNavigationBase, SelectedIndexChangedEventData } from "../tab-navigation-base/tab-navigation-base"; - import { TabContentItem } from "../tab-navigation-base/tab-content-item"; - import { TabStrip } from "../tab-navigation-base/tab-strip"; +import { EventData, Property } from "../core/view"; +import { TabContentItem } from "../tab-navigation-base/tab-content-item"; +import { + SelectedIndexChangedEventData, TabNavigationBase +} from "../tab-navigation-base/tab-navigation-base"; +import { TabStrip } from "../tab-navigation-base/tab-strip"; - export * from "../tab-navigation-base/tab-content-item"; - export * from "../tab-navigation-base/tab-navigation-base"; - export * from "../tab-navigation-base/tab-strip"; - export * from "../tab-navigation-base/tab-strip-item"; - - /** - * Represents a swipeable tabs view. - */ - export class Tabs extends TabNavigationBase { - /** - * Gets or sets the items of the Tabs. - */ - items: Array; - - /** - * Gets or sets the tab strip of the Tabs. - */ - tabStrip: TabStrip; - - /** - * Gets or sets the selectedIndex of the Tabs. - */ - selectedIndex: number; +export * from "../tab-navigation-base/tab-content-item"; +export * from "../tab-navigation-base/tab-navigation-base"; +export * from "../tab-navigation-base/tab-strip"; +export * from "../tab-navigation-base/tab-strip-item"; - /** - * Gets or sets the swipe enabled state of the Tabs. - */ - swipeEnabled: boolean; +/** + * Represents a swipeable tabs view. + */ +export class Tabs extends TabNavigationBase { + /** + * Gets or sets the items of the Tabs. + */ + items: Array; - /** - * Gets or sets the number of offscreen preloaded tabs of the Tabs. - */ - offscreenTabLimit: number; + /** + * Gets or sets the tab strip of the Tabs. + */ + tabStrip: TabStrip; - /** - * Gets or sets the position state of the Tabs. - */ - tabsPosition: "top" | "bottom"; - - /** - * 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. - */ - android: any /* android.view.View */; //android.support.v4.view.ViewPager; - - /** - * Gets the native iOS [UITabBarController](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITabBarController_Class/) that represents the user interface for this component. Valid only when running on iOS. - */ - ios: any /* UITabBarController */; - - /** - * String value used when hooking to the selectedIndexChanged event. - */ - public static selectedIndexChangedEvent: string; - - /** - * A basic method signature to hook an event listener (shortcut alias to the addEventListener method). - * @param eventNames - String corresponding to events (e.g. "propertyChange"). Optionally could be used more events separated by `,` (e.g. "propertyChange", "change"). - * @param callback - Callback function which will be executed when event is raised. - * @param thisArg - An optional parameter which will be used as `this` context for callback execution. - */ - on(eventNames: string, callback: (data: EventData) => void, thisArg?: any); - - /** - * Raised when the selected index changes. - */ - on(event: "selectedIndexChanged", callback: (args: SelectedIndexChangedEventData) => void, thisArg?: any); - } - - export const itemsProperty: Property; - export const tabStripProperty: Property - export const selectedIndexProperty: Property; + /** + * Gets or sets the selectedIndex of the Tabs. + */ + selectedIndex: number; + + /** + * Gets or sets the swipe enabled state of the Tabs. + */ + swipeEnabled: boolean; + + /** + * Gets or sets the number of offscreen preloaded tabs of the Tabs. + */ + offscreenTabLimit: number; + + /** + * Gets or sets the position state of the Tabs. + */ + tabsPosition: "top" | "bottom"; + + /** + * 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. + */ + android: any /* android.view.View */; //android.support.v4.view.ViewPager; + + /** + * Gets the native iOS [UITabBarController](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITabBarController_Class/) that represents the user interface for this component. Valid only when running on iOS. + */ + ios: any /* UITabBarController */; + + /** + * String value used when hooking to the selectedIndexChanged event. + */ + public static selectedIndexChangedEvent: string; + + /** + * A basic method signature to hook an event listener (shortcut alias to the addEventListener method). + * @param eventNames - String corresponding to events (e.g. "propertyChange"). Optionally could be used more events separated by `,` (e.g. "propertyChange", "change"). + * @param callback - Callback function which will be executed when event is raised. + * @param thisArg - An optional parameter which will be used as `this` context for callback execution. + */ + on(eventNames: string, callback: (data: EventData) => void, thisArg?: any); + + /** + * Raised when the selected index changes. + */ + on(event: "selectedIndexChanged", callback: (args: SelectedIndexChangedEventData) => void, thisArg?: any); +} + +export const itemsProperty: Property; +export const tabStripProperty: Property +export const selectedIndexProperty: Property; diff --git a/tns-core-modules/ui/tabs/tabs.ios.ts b/tns-core-modules/ui/tabs/tabs.ios.ts index 38eea43495..c96249f6ed 100644 --- a/tns-core-modules/ui/tabs/tabs.ios.ts +++ b/tns-core-modules/ui/tabs/tabs.ios.ts @@ -81,23 +81,11 @@ class UIPageViewControllerImpl extends UIPageViewController { tabBar.items = NSArray.arrayWithArray(tabBarItems); } - // tabBar.items = >NSArray.alloc().initWithArray([ - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // ]); - tabBar.delegate = this.tabBarDelegate = MDCTabBarDelegateImpl.initWithOwner(new WeakRef(owner)); - tabBar.itemAppearance = MDCTabBarItemAppearance.Titles; + // Initially set `itemAppearance` to TitledImages. + // Reassign if needed when items available. + // Other combinations do not work. + tabBar.itemAppearance = MDCTabBarItemAppearance.TitledImages; tabBar.tintColor = UIColor.blueColor; tabBar.barTintColor = UIColor.whiteColor; tabBar.setTitleColorForState(UIColor.blackColor, MDCTabBarItemState.Normal); @@ -856,33 +844,20 @@ export class Tabs extends TabsBase { public setTabStripItems(items: Array) { const tabBarItems = []; - items.forEach((item: TabStripItem, i, arr) => { - const tabBarItem = UITabBarItem.alloc().initWithTitleImageTag(item.title, null, 0); + items.forEach((item: TabStripItem, i) => { + const tabBarItem = this.createTabBarItem(item, i); tabBarItems.push(tabBarItem); item.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.tabStrip.setNativeView(this.viewController.tabBar); } - // tabBar.items = >NSArray.alloc().initWithArray([ - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // UITabBarItem.alloc().initWithTitleImageTag("Test", null, 0), - // ]); - // const length = items ? items.length : 0; // if (length === 0) { // this._tabLayout.setItems(null, null); @@ -905,6 +880,41 @@ export class Tabs extends TabsBase { // }); } + private createTabBarItem(item: TabStripItem, index: number): UITabBarItem { + let image: UIImage; + let title: string; + + // Image and Label children of TabStripItem + // take priority over its `iconSource` and `title` properties + image = item.image ? this._getIcon(item.image.src) : this._getIcon(item.iconSource); + title = item.label ? item.label.text : item.title; + + if (!this.tabStrip._hasImage) { + this.tabStrip._hasImage = !!image; + } + + if (!this.tabStrip._hasTitle) { + this.tabStrip._hasTitle = !!title; + } + + const tabBarItem = UITabBarItem.alloc().initWithTitleImageTag(title, image, index); + + return tabBarItem; + } + + private _getTabBarItemAppearance(): MDCTabBarItemAppearance { + let itemAppearance; + if (this.tabStrip._hasImage && this.tabStrip._hasTitle) { + itemAppearance = MDCTabBarItemAppearance.TitledImages; + } else if (this.tabStrip._hasImage) { + itemAppearance = MDCTabBarItemAppearance.Images; + } else { + itemAppearance = MDCTabBarItemAppearance.Titles; + } + + return itemAppearance; + } + private _getIconRenderingMode(): UIImageRenderingMode { return UIImageRenderingMode.AlwaysOriginal; } @@ -1059,4 +1069,4 @@ export class Tabs extends TabsBase { // function applyStatesToItem(item: UITabBarItem, states: TabStates) { // item.setTitleTextAttributesForState(states.normalState, UIControlState.Normal); // item.setTitleTextAttributesForState(states.selectedState, UIControlState.Selected); -// } \ No newline at end of file +// }