Skip to content

feat(ios): Added background-image support for iOS action bar #10645

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 143 additions & 19 deletions packages/core/ui/action-bar/index.ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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 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;
}
}

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

const color_ = color instanceof Color ? color.ios : color;
if (__VISIONOS__ || majorVersion >= 15) {
const appearance = this._getAppearance(navBar);
// appearance.configureWithOpaqueBackground();
appearance.backgroundColor = color_;
appearance.backgroundImage = image;
this._updateAppearance(navBar, appearance);
} else {
// legacy styling
navBar.barTintColor = color_;

// 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;
}
}

Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions packages/core/ui/styling/background.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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;
Expand Down
Loading
Loading