mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-14 01:43:14 +08:00
feat(ios): background-image support for action bar (#10645)
This commit is contained in:

committed by
GitHub

parent
24ad6e45b1
commit
5e85d8873c
@ -2,7 +2,10 @@ import { IOSActionItemSettings, ActionItem as ActionItemDefinition } from '.';
|
||||
import { ActionItemBase, ActionBarBase, isVisible, flatProperty, iosIconRenderingModeProperty, traceMissingIcon } from './action-bar-common';
|
||||
import { View } from '../core/view';
|
||||
import { Color } from '../../color';
|
||||
import { colorProperty, backgroundColorProperty, backgroundInternalProperty } from '../styling/style-properties';
|
||||
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 { accessibilityHintProperty, accessibilityLabelProperty, accessibilityLanguageProperty, accessibilityValueProperty } from '../../accessibility/accessibility-properties';
|
||||
@ -12,6 +15,10 @@ export * from './action-bar-common';
|
||||
const majorVersion = iOSNativeHelper.MajorVersion;
|
||||
const UNSPECIFIED = layout.makeMeasureSpec(0, layout.UNSPECIFIED);
|
||||
|
||||
interface NSUINavigationBar extends UINavigationBar {
|
||||
gradientLayer?: CAGradientLayer;
|
||||
}
|
||||
|
||||
function loadActionIcon(item: ActionItemDefinition): any /* UIImage */ {
|
||||
let is = null;
|
||||
let img = null;
|
||||
@ -140,6 +147,15 @@ export class ActionBar extends ActionBarBase {
|
||||
return this.ios;
|
||||
}
|
||||
|
||||
public disposeNativeView() {
|
||||
const navBar = this.navBar as NSUINavigationBar;
|
||||
if (navBar?.gradientLayer) {
|
||||
navBar.gradientLayer = null;
|
||||
}
|
||||
|
||||
super.disposeNativeView();
|
||||
}
|
||||
|
||||
public _addChildFromBuilder(name: string, value: any) {
|
||||
if (value instanceof NavigationButton) {
|
||||
this.navigationButton = value;
|
||||
@ -151,12 +167,12 @@ export class ActionBar extends ActionBarBase {
|
||||
}
|
||||
|
||||
public get _getActualSize(): { width: number; height: number } {
|
||||
const navBar = this.ios;
|
||||
if (!navBar) {
|
||||
const nativeView = this.ios;
|
||||
if (!nativeView) {
|
||||
return { width: 0, height: 0 };
|
||||
}
|
||||
|
||||
const frame = navBar.frame;
|
||||
const frame = nativeView.frame;
|
||||
const size = frame.size;
|
||||
const width = layout.toDevicePixels(size.width);
|
||||
const height = layout.toDevicePixels(size.height);
|
||||
@ -273,7 +289,7 @@ export class ActionBar extends ActionBarBase {
|
||||
this.populateMenuItems(navigationItem);
|
||||
|
||||
// update colors explicitly - they may have to be cleared form a previous page
|
||||
this.updateColors(navigationBar);
|
||||
this.updateFills(navigationBar);
|
||||
|
||||
// the 'flat' property may have changed in between pages
|
||||
this.updateFlatness(navigationBar);
|
||||
@ -345,12 +361,14 @@ export class ActionBar extends ActionBarBase {
|
||||
return barButtonItem;
|
||||
}
|
||||
|
||||
private updateColors(navBar: UINavigationBar) {
|
||||
private updateFills(navBar: UINavigationBar) {
|
||||
const color = this.color;
|
||||
this.setColor(navBar, color);
|
||||
|
||||
const bgColor = <Color>this.backgroundColor;
|
||||
this.setBackgroundColor(navBar, bgColor);
|
||||
this._setBackgroundColor(navBar, this.style.backgroundColor);
|
||||
this._createBackgroundUIImage(navBar, this.style.backgroundImage, (image: UIImage) => {
|
||||
this._setBackgroundImage(navBar, image);
|
||||
});
|
||||
}
|
||||
|
||||
private setColor(navBar: UINavigationBar, color?: Color) {
|
||||
@ -373,20 +391,112 @@ export class ActionBar extends ActionBarBase {
|
||||
}
|
||||
}
|
||||
|
||||
private setBackgroundColor(navBar: UINavigationBar, color?: UIColor | Color) {
|
||||
private _setBackgroundColor(navBar: UINavigationBar, color?: UIColor | Color) {
|
||||
if (!navBar) {
|
||||
return;
|
||||
}
|
||||
|
||||
const color_ = color instanceof Color ? color.ios : color;
|
||||
const nativeColor = color instanceof Color ? color.ios : color;
|
||||
if (__VISIONOS__ || majorVersion >= 15) {
|
||||
const appearance = this._getAppearance(navBar);
|
||||
// appearance.configureWithOpaqueBackground();
|
||||
appearance.backgroundColor = color_;
|
||||
appearance.backgroundColor = nativeColor;
|
||||
this._updateAppearance(navBar, appearance);
|
||||
} else {
|
||||
// legacy styling
|
||||
navBar.barTintColor = color_;
|
||||
navBar.barTintColor = nativeColor;
|
||||
}
|
||||
}
|
||||
|
||||
private _getBackgroundColor(navBar: UINavigationBar) {
|
||||
if (!navBar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let color: UIColor;
|
||||
|
||||
if (__VISIONOS__ || majorVersion >= 15) {
|
||||
const appearance = this._getAppearance(navBar);
|
||||
color = appearance.backgroundColor;
|
||||
} else {
|
||||
// legacy styling
|
||||
color = navBar.barTintColor;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
private _setBackgroundImage(navBar: UINavigationBar, image: UIImage) {
|
||||
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, UIBarMetrics.Default);
|
||||
}
|
||||
}
|
||||
|
||||
private _getBackgroundImage(navBar: UINavigationBar) {
|
||||
if (!navBar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let image: UIImage;
|
||||
|
||||
if (__VISIONOS__ || majorVersion >= 15) {
|
||||
const appearance = this._getAppearance(navBar);
|
||||
image = appearance.backgroundImage;
|
||||
} else {
|
||||
// legacy styling
|
||||
image = navBar.backgroundImageForBarMetrics(UIBarMetrics.Default);
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
private _createBackgroundUIImage(navBar: NSUINavigationBar, value: string | LinearGradient, callback: (image: UIImage) => void): void {
|
||||
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: UIGraphicsRendererContext) => {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -411,7 +521,10 @@ export class ActionBar extends ActionBarBase {
|
||||
appearance.shadowColor = UIColor.clearColor;
|
||||
this._updateAppearance(navBar, appearance);
|
||||
} else {
|
||||
navBar.setBackgroundImageForBarMetrics(UIImage.new(), UIBarMetrics.Default);
|
||||
// Do not apply blank image if background image is already set
|
||||
if (!this.backgroundImage) {
|
||||
navBar.setBackgroundImageForBarMetrics(UIImage.new(), UIBarMetrics.Default);
|
||||
}
|
||||
navBar.shadowImage = UIImage.new();
|
||||
navBar.translucent = false;
|
||||
}
|
||||
@ -424,7 +537,11 @@ export class ActionBar extends ActionBarBase {
|
||||
this._updateAppearance(navBar, appearance);
|
||||
}
|
||||
} else {
|
||||
navBar.setBackgroundImageForBarMetrics(null, null);
|
||||
// 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, UIBarMetrics.Default);
|
||||
}
|
||||
navBar.shadowImage = null;
|
||||
navBar.translucent = true;
|
||||
}
|
||||
@ -507,13 +624,21 @@ export class ActionBar extends ActionBarBase {
|
||||
}
|
||||
|
||||
[backgroundColorProperty.getDefault](): UIColor {
|
||||
// This getter is never called.
|
||||
// CssAnimationProperty use default value form their constructor.
|
||||
return null;
|
||||
return this._getBackgroundColor(this.navBar);
|
||||
}
|
||||
[backgroundColorProperty.setNative](color: UIColor | Color) {
|
||||
this._setBackgroundColor(this.navBar, color);
|
||||
}
|
||||
|
||||
[backgroundImageProperty.getDefault](): UIImage {
|
||||
return this._getBackgroundImage(this.navBar);
|
||||
}
|
||||
[backgroundImageProperty.setNative](value: string | LinearGradient) {
|
||||
const navBar = this.navBar;
|
||||
this.setBackgroundColor(navBar, color);
|
||||
|
||||
this._createBackgroundUIImage(navBar, value, (image: UIImage) => {
|
||||
this._setBackgroundImage(navBar, image);
|
||||
});
|
||||
}
|
||||
|
||||
[backgroundInternalProperty.getDefault](): UIColor {
|
||||
@ -524,7 +649,6 @@ export class ActionBar extends ActionBarBase {
|
||||
}
|
||||
|
||||
[flatProperty.setNative](value: boolean) {
|
||||
// tslint:disable-line
|
||||
const navBar = this.navBar;
|
||||
if (navBar) {
|
||||
this.updateFlatness(navBar);
|
||||
|
2
packages/core/ui/styling/background.d.ts
vendored
2
packages/core/ui/styling/background.d.ts
vendored
@ -3,6 +3,7 @@ import { View } from '../core/view';
|
||||
import { BackgroundRepeat } from '../../css/parser';
|
||||
import { LinearGradient } from '../styling/linear-gradient';
|
||||
import { BoxShadow } from './box-shadow';
|
||||
import { Background as BackgroundDefinition } from './background-common';
|
||||
|
||||
export * from './background-common';
|
||||
|
||||
@ -78,6 +79,7 @@ export namespace ios {
|
||||
export function createBackgroundUIColor(view: View, callback: (uiColor: any /* UIColor */) => void, flip?: boolean): void;
|
||||
export function drawBackgroundVisualEffects(view: View): void;
|
||||
export function clearBackgroundVisualEffects(view: View): void;
|
||||
export function createUIImageFromURI(view: View, imageURI: string, flip: boolean, callback: (image: any) => void): void;
|
||||
export function generateClipPath(view: View, bounds: CGRect): any;
|
||||
export function generateShadowLayerPaths(view: View, bounds: CGRect): { maskPath: any; shadowPath: any };
|
||||
export function getUniformBorderRadius(view: View, bounds: CGRect): number;
|
||||
|
@ -69,7 +69,9 @@ export namespace ios {
|
||||
callback(background?.color?.ios);
|
||||
} else {
|
||||
if (!(background.image instanceof LinearGradient)) {
|
||||
setUIColorFromImage(view, nativeView, callback, flip);
|
||||
createUIImageFromURI(view, background.image, flip, (image: UIImage) => {
|
||||
callback(image ? UIColor.alloc().initWithPatternImage(image) : background?.color?.ios);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -174,6 +176,52 @@ export namespace ios {
|
||||
background.clearFlags = BackgroundClearFlags.NONE;
|
||||
}
|
||||
|
||||
export function createUIImageFromURI(view: View, imageURI: string, flip: boolean, callback: (image: UIImage) => void): void {
|
||||
const nativeView: UIView = view.nativeViewProtected;
|
||||
if (!nativeView) {
|
||||
return;
|
||||
}
|
||||
|
||||
const frame = nativeView.frame;
|
||||
const boundsWidth = view.scaleX ? frame.size.width / view.scaleX : frame.size.width;
|
||||
const boundsHeight = view.scaleY ? frame.size.height / view.scaleY : frame.size.height;
|
||||
if (!boundsWidth || !boundsHeight) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const style = view.style;
|
||||
|
||||
if (imageURI) {
|
||||
const match = imageURI.match(uriPattern);
|
||||
if (match && match[2]) {
|
||||
imageURI = match[2];
|
||||
}
|
||||
}
|
||||
|
||||
let bitmap: UIImage;
|
||||
if (isDataURI(imageURI)) {
|
||||
const base64Data = imageURI.split(',')[1];
|
||||
if (base64Data !== undefined) {
|
||||
const imageSource = ImageSource.fromBase64Sync(base64Data);
|
||||
bitmap = imageSource && imageSource.ios;
|
||||
}
|
||||
} else if (isFileOrResourcePath(imageURI)) {
|
||||
const imageSource = ImageSource.fromFileOrResourceSync(imageURI);
|
||||
bitmap = imageSource && imageSource.ios;
|
||||
} else if (imageURI.indexOf('http') !== -1) {
|
||||
style[symbolUrl] = imageURI;
|
||||
ImageSource.fromUrl(imageURI)
|
||||
.then((r) => {
|
||||
if (style && style[symbolUrl] === imageURI) {
|
||||
callback(generatePatternImage(r.ios, view, flip));
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
callback(generatePatternImage(bitmap, view, flip));
|
||||
}
|
||||
|
||||
export function generateShadowLayerPaths(view: View, bounds: CGRect): { maskPath: any; shadowPath: any } {
|
||||
const background = view.style.backgroundInternal;
|
||||
const nativeView = <NativeScriptUIView>view.nativeViewProtected;
|
||||
@ -423,48 +471,6 @@ function clearNonUniformBorders(nativeView: NativeScriptUIView): void {
|
||||
nativeView.hasNonUniformBorder = false;
|
||||
}
|
||||
|
||||
function setUIColorFromImage(view: View, nativeView: UIView, callback: (uiColor: UIColor) => void, flip?: boolean): void {
|
||||
const frame = nativeView.frame;
|
||||
const boundsWidth = view.scaleX ? frame.size.width / view.scaleX : frame.size.width;
|
||||
const boundsHeight = view.scaleY ? frame.size.height / view.scaleY : frame.size.height;
|
||||
if (!boundsWidth || !boundsHeight) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const style = view.style;
|
||||
const background = style.backgroundInternal;
|
||||
let imageUri = background.image as string;
|
||||
if (imageUri) {
|
||||
const match = imageUri.match(uriPattern);
|
||||
if (match && match[2]) {
|
||||
imageUri = match[2];
|
||||
}
|
||||
}
|
||||
|
||||
let bitmap: UIImage;
|
||||
if (isDataURI(imageUri)) {
|
||||
const base64Data = imageUri.split(',')[1];
|
||||
if (base64Data !== undefined) {
|
||||
const imageSource = ImageSource.fromBase64Sync(base64Data);
|
||||
bitmap = imageSource && imageSource.ios;
|
||||
}
|
||||
} else if (isFileOrResourcePath(imageUri)) {
|
||||
const imageSource = ImageSource.fromFileOrResourceSync(imageUri);
|
||||
bitmap = imageSource && imageSource.ios;
|
||||
} else if (imageUri.indexOf('http') !== -1) {
|
||||
style[symbolUrl] = imageUri;
|
||||
ImageSource.fromUrl(imageUri)
|
||||
.then((r) => {
|
||||
if (style && style[symbolUrl] === imageUri) {
|
||||
uiColorFromImage(r.ios, view, callback, flip);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
uiColorFromImage(bitmap, view, callback, flip);
|
||||
}
|
||||
|
||||
function parsePosition(pos: string): { x: CSSValue; y: CSSValue } {
|
||||
const values = cssParse(pos);
|
||||
if (values.length === 2) {
|
||||
@ -610,14 +616,12 @@ function getDrawParams(this: void, image: UIImage, background: BackgroundDefinit
|
||||
return res;
|
||||
}
|
||||
|
||||
function uiColorFromImage(img: UIImage, view: View, callback: (uiColor: UIColor) => void, flip?: boolean): void {
|
||||
function generatePatternImage(img: UIImage, view: View, flip?: boolean): UIImage {
|
||||
const background = view.style.backgroundInternal;
|
||||
const nativeView: NativeScriptUIView = view.nativeViewProtected;
|
||||
|
||||
if (!img || !nativeView) {
|
||||
callback(background.color && background.color.ios);
|
||||
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
const frame = nativeView.frame;
|
||||
@ -658,15 +662,10 @@ function uiColorFromImage(img: UIImage, view: View, callback: (uiColor: UIColor)
|
||||
img.drawAsPatternInRect(patternRect);
|
||||
}
|
||||
|
||||
const bkgImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
const bgImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
if (flip) {
|
||||
const flippedImage = _flipImage(bkgImage);
|
||||
callback(UIColor.alloc().initWithPatternImage(flippedImage));
|
||||
} else {
|
||||
callback(UIColor.alloc().initWithPatternImage(bkgImage));
|
||||
}
|
||||
return flip ? _flipImage(bgImage) : bgImage;
|
||||
}
|
||||
|
||||
// Flipping the default coordinate system
|
||||
@ -820,7 +819,7 @@ function calculateInnerBorderClipRadius(radius: number, insetX: number, insetY:
|
||||
* @param offset
|
||||
* @returns
|
||||
*/
|
||||
export function generateNonUniformBorderOuterClipPath(bounds: CGRect, cappedRadii: CappedOuterRadii, offset: number = 0): any {
|
||||
function generateNonUniformBorderOuterClipPath(bounds: CGRect, cappedRadii: CappedOuterRadii, offset: number = 0): any {
|
||||
const { width, height } = bounds.size;
|
||||
const { x, y } = bounds.origin;
|
||||
|
||||
|
@ -782,7 +782,7 @@ export const backgroundImageProperty = new CssProperty<Style, string | LinearGra
|
||||
return value1 === value2;
|
||||
}
|
||||
},
|
||||
valueConverter: (value: any) => {
|
||||
valueConverter: (value: string | LinearGradient) => {
|
||||
if (typeof value === 'string') {
|
||||
const parsed = parseBackground(value);
|
||||
if (parsed) {
|
||||
|
2
packages/core/ui/utils.d.ts
vendored
2
packages/core/ui/utils.d.ts
vendored
@ -40,5 +40,5 @@ export namespace ios {
|
||||
* @param gradient Parsed LinearGradient
|
||||
* @param gradientLayerOpacity Initial layer opacity (in case you'd like to use with animation sequence)
|
||||
*/
|
||||
export function drawGradient(uiView: NativeScriptUIView, gradientLayer: CAGradientLayer, gradient: LinearGradient, gradientLayerOpacity?: number): void;
|
||||
export function drawGradient(uiView: any /* UIView */, gradientLayer: CAGradientLayer, gradient: LinearGradient, gradientLayerOpacity?: number): void;
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ export namespace ios {
|
||||
return utils.layout.toDevicePixels(min);
|
||||
}
|
||||
|
||||
export function drawGradient(nativeView: NativeScriptUIView, gradientLayer: CAGradientLayer, gradient: LinearGradient, gradientLayerOpacity?: number): void {
|
||||
export function drawGradient(nativeView: UIView, gradientLayer: CAGradientLayer, gradient: LinearGradient, gradientLayerOpacity?: number): void {
|
||||
if (!nativeView || !gradient) {
|
||||
return;
|
||||
}
|
||||
|
Reference in New Issue
Block a user