@nativescript/core
Version:
A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.
591 lines • 23.3 kB
JavaScript
import { ActionItemBase, ActionBarBase, isVisible, flatProperty, iosIconRenderingModeProperty, traceMissingIcon, iosLargeTitleProperty } from './action-bar-common';
import { View } from '../core/view';
import { Color } from '../../color';
import { ios as iosBackground } from '../styling/background';
import { LinearGradient } from '../styling/linear-gradient';
import { colorProperty, backgroundInternalProperty, backgroundColorProperty, backgroundImageProperty } from '../styling/style-properties';
import { ios as iosViewUtils } from '../utils';
import { ImageSource } from '../../image-source';
import { layout, iOSNativeHelper, isFontIconURI } from '../../utils';
import { SDK_VERSION } from '../../utils/constants';
import { accessibilityHintProperty, accessibilityLabelProperty, accessibilityLanguageProperty, accessibilityValueProperty } from '../../accessibility/accessibility-properties';
export * from './action-bar-common';
const majorVersion = iOSNativeHelper.MajorVersion;
const UNSPECIFIED = layout.makeMeasureSpec(0, layout.UNSPECIFIED);
function loadActionIcon(item) {
let is = null;
let img = null;
const itemIcon = item.icon;
const itemStyle = item.style;
if (isFontIconURI(itemIcon)) {
const fontIconCode = itemIcon.split('//')[1];
const font = itemStyle.fontInternal;
const color = itemStyle.color;
is = ImageSource.fromFontIconCodeSync(fontIconCode, font, color);
}
else {
is = ImageSource.fromFileOrResourceSync(itemIcon);
}
if (is && is.ios) {
img = is.ios;
}
else {
traceMissingIcon(itemIcon);
}
return img;
}
var TapBarItemHandlerImpl = /** @class */ (function (_super) {
__extends(TapBarItemHandlerImpl, _super);
function TapBarItemHandlerImpl() {
return _super !== null && _super.apply(this, arguments) || this;
}
TapBarItemHandlerImpl.initWithOwner = function (owner) {
var handler = TapBarItemHandlerImpl.new();
handler._owner = owner;
return handler;
};
TapBarItemHandlerImpl.prototype.tap = function (args) {
var _a;
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (owner) {
owner._raiseTap();
}
};
TapBarItemHandlerImpl.ObjCExposedMethods = {
tap: { returns: interop.types.void, params: [interop.types.id] },
};
return TapBarItemHandlerImpl;
}(NSObject));
export class ActionItem extends ActionItemBase {
constructor() {
super(...arguments);
this._ios = {
position: 'left',
systemIcon: undefined,
};
}
// @ts-ignore
get ios() {
return this._ios;
}
set ios(value) {
throw new Error('ActionItem.ios is read-only');
}
}
export class NavigationButton extends ActionItem {
_onVisibilityChanged(visibility) {
if (this._navigationItem) {
const visible = visibility === 'visible';
this._navigationItem.setHidesBackButtonAnimated(!visible, true);
}
}
}
export class ActionBar extends ActionBarBase {
get ios() {
const page = this.page;
if (!page || !page.parent) {
return;
}
const viewController = page.ios;
return viewController?.navigationController?.navigationBar;
}
[accessibilityValueProperty.setNative](value) {
value = value == null ? null : `${value}`;
this.nativeViewProtected.accessibilityValue = value;
const navigationItem = this._getNavigationItem();
if (navigationItem) {
navigationItem.accessibilityValue = value;
}
}
[accessibilityLabelProperty.setNative](value) {
value = value == null ? null : `${value}`;
this.nativeViewProtected.accessibilityLabel = value;
const navigationItem = this._getNavigationItem();
if (navigationItem) {
navigationItem.accessibilityLabel = value;
}
}
[accessibilityHintProperty.setNative](value) {
value = value == null ? null : `${value}`;
this.nativeViewProtected.accessibilityHint = value;
const navigationItem = this._getNavigationItem();
if (navigationItem) {
navigationItem.accessibilityHint = value;
}
}
[accessibilityLanguageProperty.setNative](value) {
value = value == null ? null : `${value}`;
this.nativeViewProtected.accessibilityLanguage = value;
const navigationItem = this._getNavigationItem();
if (navigationItem) {
navigationItem.accessibilityLanguage = value;
}
}
createNativeView() {
return this.ios;
}
disposeNativeView() {
const navBar = this.navBar;
if (navBar?.gradientLayer) {
navBar.gradientLayer = null;
}
super.disposeNativeView();
}
_addChildFromBuilder(name, value) {
if (value instanceof NavigationButton) {
this.navigationButton = value;
}
else if (value instanceof ActionItem) {
this.actionItems?.addItem(value);
}
else if (value instanceof View) {
this.titleView = value;
}
}
get _getActualSize() {
const nativeView = this.ios;
if (!nativeView) {
return { width: 0, height: 0 };
}
const frame = nativeView.frame;
const size = frame.size;
const width = layout.toDevicePixels(size.width);
const height = layout.toDevicePixels(size.height);
return { width, height };
}
layoutInternal() {
const { width, height } = this._getActualSize;
const widthSpec = layout.makeMeasureSpec(width, layout.EXACTLY);
const heightSpec = layout.makeMeasureSpec(height, layout.EXACTLY);
this.measure(widthSpec, heightSpec);
this.layout(0, 0, width, height, false);
}
_getIconRenderingMode() {
switch (this.iosIconRenderingMode) {
case 'alwaysOriginal':
return 1 /* UIImageRenderingMode.AlwaysOriginal */;
case 'alwaysTemplate':
return 2 /* UIImageRenderingMode.AlwaysTemplate */;
case 'automatic':
default:
return 1 /* UIImageRenderingMode.AlwaysOriginal */;
}
}
_getNavigationItem() {
const page = this.page;
// Page should be attached to frame to update the action bar.
if (!page || !page.frame) {
return null;
}
const viewController = page.ios;
return viewController.navigationItem;
}
update() {
const page = this.page;
// Page should be attached to frame to update the action bar.
if (!page || !page.frame) {
return;
}
const viewController = page.ios;
// visionOS may init with different nav stack setup
if (!viewController) {
return;
}
const navigationItem = viewController.navigationItem;
const navController = viewController.navigationController;
if (!navController) {
return;
}
const navigationBar = navController.navigationBar;
let previousController;
// Set Title
navigationItem.title = this.title;
const titleView = this.titleView;
if (titleView && titleView.ios) {
navigationItem.titleView = titleView.ios;
}
else {
navigationItem.titleView = null;
}
// Find previous ViewController in the navigation stack
const indexOfViewController = navController.viewControllers.indexOfObject(viewController);
if (indexOfViewController > 0 && indexOfViewController < navController.viewControllers.count) {
previousController = navController.viewControllers[indexOfViewController - 1];
}
// Set back button text
if (previousController) {
if (this.navigationButton) {
const tapHandler = TapBarItemHandlerImpl.initWithOwner(new WeakRef(this.navigationButton));
const barButtonItem = UIBarButtonItem.alloc().initWithTitleStyleTargetAction(this.navigationButton.text + '', 0 /* UIBarButtonItemStyle.Plain */, tapHandler, 'tap');
previousController.navigationItem.backBarButtonItem = barButtonItem;
}
else {
previousController.navigationItem.backBarButtonItem = null;
}
}
// Set back button image
let img;
if (this.navigationButton && isVisible(this.navigationButton) && this.navigationButton.icon) {
img = loadActionIcon(this.navigationButton);
}
// TODO: This could cause issue when canceling BackEdge gesture - we will change the backIndicator to
// show the one from the old page but the new page will still be visible (because we canceled EdgeBackSwipe gesutre)
// Consider moving this to new method and call it from - navigationControllerDidShowViewControllerAnimated.
const image = img ? img.imageWithRenderingMode(1 /* UIImageRenderingMode.AlwaysOriginal */) : null;
if (majorVersion >= 15) {
const appearance = this._getAppearance(navigationBar);
appearance.setBackIndicatorImageTransitionMaskImage(image, image);
this._updateAppearance(navigationBar, appearance);
}
else {
navigationBar.backIndicatorImage = image;
navigationBar.backIndicatorTransitionMaskImage = image;
}
// Set back button visibility
if (this.navigationButton) {
this.navigationButton._navigationItem = navigationItem;
navigationItem.setHidesBackButtonAnimated(!isVisible(this.navigationButton), false);
}
// Populate action items
this.populateMenuItems(navigationItem);
// update colors explicitly - they may have to be cleared form a previous page
this.updateFills(navigationBar);
// the 'flat' property may have changed in between pages
this.updateFlatness(navigationBar);
if (!this.isLayoutValid) {
this.layoutInternal();
}
// Make sure accessibility values are up-to-date on the navigationItem
navigationItem.accessibilityValue = this.accessibilityValue;
navigationItem.accessibilityLabel = this.accessibilityLabel;
navigationItem.accessibilityLanguage = this.accessibilityLanguage;
navigationItem.accessibilityHint = this.accessibilityHint;
}
populateMenuItems(navigationItem) {
const items = this.actionItems?.getVisibleItems() ?? [];
const leftBarItems = NSMutableArray.new();
const rightBarItems = NSMutableArray.new();
for (let i = 0; i < items.length; i++) {
const barButtonItem = this.createBarButtonItem(items[i]);
if (items[i].ios.position === 'left') {
leftBarItems.addObject(barButtonItem);
}
else {
rightBarItems.insertObjectAtIndex(barButtonItem, 0);
}
}
navigationItem.setLeftBarButtonItemsAnimated(leftBarItems, false);
navigationItem.setRightBarButtonItemsAnimated(rightBarItems, false);
if (leftBarItems.count > 0) {
navigationItem.leftItemsSupplementBackButton = true;
}
}
createBarButtonItem(item) {
const tapHandler = TapBarItemHandlerImpl.initWithOwner(new WeakRef(item));
// associate handler with menuItem or it will get collected by JSC.
item.handler = tapHandler;
let barButtonItem;
if (item.actionView && item.actionView.ios) {
const recognizer = UITapGestureRecognizer.alloc().initWithTargetAction(tapHandler, 'tap');
item.actionView.ios.addGestureRecognizer(recognizer);
barButtonItem = UIBarButtonItem.alloc().initWithCustomView(item.actionView.ios);
}
else if (item.ios.systemIcon !== undefined) {
let id = item.ios.systemIcon;
if (typeof id === 'string') {
id = parseInt(id);
}
barButtonItem = UIBarButtonItem.alloc().initWithBarButtonSystemItemTargetAction(id, tapHandler, 'tap');
}
else if (item.icon) {
const img = loadActionIcon(item);
if (img) {
const image = img.imageWithRenderingMode(this._getIconRenderingMode());
barButtonItem = UIBarButtonItem.alloc().initWithImageStyleTargetAction(image, 0 /* UIBarButtonItemStyle.Plain */, tapHandler, 'tap');
}
}
else {
barButtonItem = UIBarButtonItem.alloc().initWithTitleStyleTargetAction(item.text + '', 0 /* UIBarButtonItemStyle.Plain */, tapHandler, 'tap');
}
if (item.text) {
barButtonItem.isAccessibilityElement = true;
barButtonItem.accessibilityLabel = item.text;
barButtonItem.accessibilityTraits = UIAccessibilityTraitButton;
}
return barButtonItem;
}
updateFills(navBar) {
const color = this.color;
this.setColor(navBar, color);
this._setBackgroundColor(navBar, this.style.backgroundColor);
this._createBackgroundUIImage(navBar, this.style.backgroundImage, (image) => {
this._setBackgroundImage(navBar, image);
});
}
setColor(navBar, color) {
if (!navBar) {
return;
}
if (color) {
const titleTextColor = NSDictionary.dictionaryWithObjectForKey(color.ios, NSForegroundColorAttributeName);
if (majorVersion >= 15) {
const appearance = this._getAppearance(navBar);
appearance.titleTextAttributes = titleTextColor;
}
navBar.titleTextAttributes = titleTextColor;
navBar.largeTitleTextAttributes = titleTextColor;
navBar.tintColor = color.ios;
}
else {
navBar.titleTextAttributes = null;
navBar.largeTitleTextAttributes = null;
navBar.tintColor = null;
}
}
_setBackgroundColor(navBar, color) {
if (!navBar) {
return;
}
const nativeColor = color instanceof Color ? color.ios : color;
if (__VISIONOS__ || majorVersion >= 15) {
const appearance = this._getAppearance(navBar);
// appearance.configureWithOpaqueBackground();
appearance.backgroundColor = nativeColor;
this._updateAppearance(navBar, appearance);
}
else {
// legacy styling
navBar.barTintColor = nativeColor;
}
}
_getBackgroundColor(navBar) {
if (!navBar) {
return null;
}
let color;
if (__VISIONOS__ || majorVersion >= 15) {
const appearance = this._getAppearance(navBar);
color = appearance.backgroundColor;
}
else {
// legacy styling
color = navBar.barTintColor;
}
return color;
}
_setBackgroundImage(navBar, image) {
if (!navBar) {
return;
}
if (__VISIONOS__ || majorVersion >= 15) {
const appearance = this._getAppearance(navBar);
// appearance.configureWithOpaqueBackground();
appearance.backgroundImage = image;
this._updateAppearance(navBar, appearance);
}
else {
// legacy styling
// Set a blank image in case image is null and flatness is enabled
if (this.flat && !image) {
image = UIImage.new();
}
navBar.setBackgroundImageForBarMetrics(image, 0 /* UIBarMetrics.Default */);
}
}
_getBackgroundImage(navBar) {
if (!navBar) {
return null;
}
let image;
if (__VISIONOS__ || majorVersion >= 15) {
const appearance = this._getAppearance(navBar);
image = appearance.backgroundImage;
}
else {
// legacy styling
image = navBar.backgroundImageForBarMetrics(0 /* UIBarMetrics.Default */);
}
return image;
}
_createBackgroundUIImage(navBar, value, callback) {
if (!navBar) {
return;
}
if (value) {
if (value instanceof LinearGradient) {
if (!navBar.gradientLayer) {
navBar.gradientLayer = CAGradientLayer.new();
}
iosViewUtils.drawGradient(navBar, navBar.gradientLayer, value);
const renderer = UIGraphicsImageRenderer.alloc().initWithSize(navBar.bounds.size);
const img = renderer.imageWithActions((context) => {
navBar.gradientLayer.renderInContext(context.CGContext);
});
callback(img);
// Return here to avoid unnecessary cleanups
return;
}
// Background image
iosBackground.createUIImageFromURI(this, value, false, callback);
}
else {
callback(null);
}
if (navBar.gradientLayer) {
navBar.gradientLayer = null;
}
}
_onTitlePropertyChanged() {
const page = this.page;
if (!page) {
return;
}
if (page.frame) {
page.frame._updateActionBar(page);
}
const navigationItem = page.ios.navigationItem;
navigationItem.title = this.title;
}
updateFlatness(navBar) {
if (this.flat) {
if (majorVersion >= 15) {
const appearance = this._getAppearance(navBar);
appearance.shadowColor = UIColor.clearColor;
this._updateAppearance(navBar, appearance);
}
else {
// Do not apply blank image if background image is already set
if (!this.backgroundImage) {
navBar.setBackgroundImageForBarMetrics(UIImage.new(), 0 /* UIBarMetrics.Default */);
}
navBar.shadowImage = UIImage.new();
navBar.translucent = false;
}
}
else {
if (majorVersion >= 15) {
if (navBar.standardAppearance) {
// Not flat and never been set do nothing.
const appearance = navBar.standardAppearance;
appearance.shadowColor = this.iosShadow ? UINavigationBarAppearance.new().shadowColor : UIColor.clearColor;
this._updateAppearance(navBar, appearance);
}
}
else {
// Do not apply blank image if background image is already set
if (!this.backgroundImage) {
// Bar metrics is needed even when unsetting the image
navBar.setBackgroundImageForBarMetrics(null, 0 /* UIBarMetrics.Default */);
}
navBar.shadowImage = null;
navBar.translucent = true;
}
}
}
_getAppearance(navBar) {
return navBar.standardAppearance ?? UINavigationBarAppearance.new();
}
_updateAppearance(navBar, appearance) {
navBar.standardAppearance = appearance;
navBar.compactAppearance = appearance;
navBar.scrollEdgeAppearance = appearance;
}
onMeasure(widthMeasureSpec, heightMeasureSpec) {
const width = layout.getMeasureSpecSize(widthMeasureSpec);
const height = layout.getMeasureSpecSize(heightMeasureSpec);
if (this.titleView) {
View.measureChild(this, this.titleView, UNSPECIFIED, UNSPECIFIED);
}
this.actionItems?.getItems().forEach((actionItem) => {
const actionView = actionItem.actionView;
if (actionView) {
View.measureChild(this, actionView, UNSPECIFIED, UNSPECIFIED);
}
});
// We ignore our width/height, minWidth/minHeight dimensions because it is against Apple policy to change height of NavigationBar.
this.setMeasuredDimension(width, height);
}
onLayout(left, top, right, bottom) {
const titleView = this.titleView;
if (titleView) {
if (majorVersion > 10) {
// On iOS 11 titleView is wrapped in another view that is centered with constraints.
View.layoutChild(this, titleView, 0, 0, titleView.getMeasuredWidth(), titleView.getMeasuredHeight());
}
else {
// On iOS <11 titleView is direct child of UINavigationBar so we give it full width and leave
// the layout to center it.
View.layoutChild(this, titleView, 0, 0, right - left, bottom - top);
}
}
this.actionItems?.getItems().forEach((actionItem) => {
const actionView = actionItem.actionView;
if (actionView && actionView.ios) {
const measuredWidth = actionView.getMeasuredWidth();
const measuredHeight = actionView.getMeasuredHeight();
View.layoutChild(this, actionView, 0, 0, measuredWidth, measuredHeight);
}
});
super.onLayout(left, top, right, bottom);
}
layoutNativeView(left, top, right, bottom) {
return;
}
get navBar() {
// Page should be attached to frame to update the action bar.
if (this.page?.frame?.ios?.controller) {
return this.page.frame.ios.controller.navigationBar;
}
else {
return undefined;
}
}
[colorProperty.getDefault]() {
return null;
}
[colorProperty.setNative](color) {
const navBar = this.navBar;
this.setColor(navBar, color);
}
[backgroundColorProperty.getDefault]() {
return this._getBackgroundColor(this.navBar);
}
[backgroundColorProperty.setNative](color) {
this._setBackgroundColor(this.navBar, color);
}
[backgroundImageProperty.getDefault]() {
return this._getBackgroundImage(this.navBar);
}
[backgroundImageProperty.setNative](value) {
const navBar = this.navBar;
this._createBackgroundUIImage(navBar, value, (image) => {
this._setBackgroundImage(navBar, image);
});
}
[backgroundInternalProperty.getDefault]() {
return null;
}
// @ts-ignore
[backgroundInternalProperty.setNative](value) { }
[flatProperty.setNative](value) {
const navBar = this.navBar;
if (navBar) {
this.updateFlatness(navBar);
}
}
[iosIconRenderingModeProperty.getDefault]() {
return 'alwaysOriginal';
}
[iosIconRenderingModeProperty.setNative](value) {
this.update();
}
[iosLargeTitleProperty.setNative](value) {
if (!this.navBar) {
return;
}
if (SDK_VERSION >= 11) {
this.navBar.prefersLargeTitles = value;
}
}
}
//# sourceMappingURL=index.ios.js.map