refactor(tabs): strip item appearance and creation (#7466)

* test(e2e): add tab strip items pages

* fix(ios-tabs): a crash on swiping a content item

Fixes https://github.com/NativeScript/NativeScript/issues/7459.

* feat(android-tabs): create tab item spec from image and label

Implements https://github.com/NativeScript/nativescript-angular/issues/1884 for Android.

* feat(ios-tabs): tab bar item appearance

Implements https://github.com/NativeScript/NativeScript/issues/7436 and https://github.com/NativeScript/nativescript-angular/issues/1884.

* refactor(ios-tabs): move _hasImage and _hasTitle properties
This commit is contained in:
Vasil Chimev
2019-07-10 16:05:45 +03:00
committed by Alexander Djenkov
parent c42bf0a6d0
commit c34a48ac77
10 changed files with 272 additions and 124 deletions

View File

@ -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 = <Page>args.object;
const wrapLayout = <WrapLayout>page.getViewById("wrapLayoutWithExamples");
@ -18,6 +19,8 @@ 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");

View File

@ -0,0 +1,16 @@
<Page>
<Tabs id="tabs">
<TabStrip>
<TabStripItem title="TabStripItem 1" iconSource="res://icon">
</TabStripItem>
</TabStrip>
<TabContentItem>
<Label text="TabContentItem 1"></Label>
</TabContentItem>
</Tabs>
</Page>

View File

@ -0,0 +1,88 @@
<Page>
<Tabs id="tabs">
<TabStrip>
<TabStripItem title="TabStripItem 1" iconSource="res://icon">
</TabStripItem>
<TabStripItem>
<Label text="TabStripItem 2">
</Label>
<Image src="res://icon">
</Image>
</TabStripItem>
<TabStripItem title="TabStripItem X" iconSource="res://icon">
<Label text="TabStripItem 3">
</Label>
<Image src="res://icon">
</Image>
</TabStripItem>
<TabStripItem>
<Label text="TabStripItem 4">
</Label>
</TabStripItem>
<TabStripItem>
<Image src="res://icon">
</Image>
</TabStripItem>
<TabStripItem title="TabStripItem 6">
</TabStripItem>
<TabStripItem iconSource="res://icon">
</TabStripItem>
<TabStripItem title="TabStripItem X" iconSource="res://icon">
<Label text="TabStripItem 8">
</Label>
</TabStripItem>
<TabStripItem title="TabStripItem 9" iconSource="res://icon">
<Image src="res://icon">
</Image>
</TabStripItem>
</TabStrip>
<TabContentItem>
<Label text="TabContentItem 1"></Label>
</TabContentItem>
<TabContentItem>
<Label text="TabContentItem 2"></Label>
</TabContentItem>
<TabContentItem>
<Label text="TabContentItem 3"></Label>
</TabContentItem>
<TabContentItem>
<Label text="TabContentItem 4"></Label>
</TabContentItem>
<TabContentItem>
<Label text="TabContentItem 5"></Label>
</TabContentItem>
<TabContentItem>
<Label text="TabContentItem 6"></Label>
</TabContentItem>
<TabContentItem>
<Label text="TabContentItem 7"></Label>
</TabContentItem>
<TabContentItem>
<Label text="TabContentItem 8"></Label>
</TabContentItem>
<TabContentItem>
<Label text="TabContentItem 9"></Label>
</TabContentItem>
</Tabs>
</Page>

View File

@ -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.
*/

View File

@ -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<TabStrip, "automatic" | "alwaysOriginal" | "alwaysTemplate">;

View File

@ -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;

View File

@ -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;
let iconSource;
const tabItemSpec = new org.nativescript.widgets.TabItemSpec();
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) {
// 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;

View File

@ -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";
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<TabContentItem>;
/**
* Represents a swipeable tabs view.
*/
export class Tabs extends TabNavigationBase {
/**
* Gets or sets the items of the Tabs.
*/
items: Array<TabContentItem>;
/**
* Gets or sets the tab strip of the Tabs.
*/
tabStrip: TabStrip;
/**
* Gets or sets the tab strip of the Tabs.
*/
tabStrip: TabStrip;
/**
* Gets or sets the selectedIndex of the Tabs.
*/
selectedIndex: number;
/**
* 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 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 number of offscreen preloaded tabs of the Tabs.
*/
offscreenTabLimit: number;
/**
* Gets or sets the position state of the Tabs.
*/
tabsPosition: "top" | "bottom";
/**
* 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 [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 */;
/**
* 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;
/**
* 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);
/**
* 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);
}
/**
* Raised when the selected index changes.
*/
on(event: "selectedIndexChanged", callback: (args: SelectedIndexChangedEventData) => void, thisArg?: any);
}
export const itemsProperty: Property<Tabs, TabContentItem[]>;
export const tabStripProperty: Property<Tabs, TabStrip>
export const selectedIndexProperty: Property<Tabs, number>;
export const itemsProperty: Property<Tabs, TabContentItem[]>;
export const tabStripProperty: Property<Tabs, TabStrip>
export const selectedIndexProperty: Property<Tabs, number>;

View File

@ -81,23 +81,11 @@ class UIPageViewControllerImpl extends UIPageViewController {
tabBar.items = NSArray.arrayWithArray(tabBarItems);
}
// tabBar.items = <NSArray<UITabBarItem>>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<TabStripItem>) {
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<UITabBarItem>>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;
}