feat(ios): iosGlassEffect property

This commit is contained in:
Nathan Walker
2025-08-20 19:38:33 -07:00
parent 991445461b
commit 0178cd35fd
9 changed files with 129 additions and 3 deletions

View File

@@ -2,10 +2,11 @@
import { Point, Position, View as ViewDefinition } from '.';
// Requires
import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty } from './view-common';
import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, iosGlassEffectProperty, GlassEffectType, GlassEffectVariant } from './view-common';
import { ShowModalOptions, hiddenProperty } from '../view-base';
import { Trace } from '../../../trace';
import { layout, ios as iosUtils, SDK_VERSION } from '../../../utils';
import { layout, ios as iosUtils } from '../../../utils';
import { SDK_VERSION, supportsGlass } from '../../../utils/constants';
import { IOSHelper } from './view-helper';
import { ios as iosBackground, Background } from '../../styling/background';
import { perspectiveProperty, visibilityProperty, opacityProperty, rotateProperty, rotateXProperty, rotateYProperty, scaleXProperty, scaleYProperty, translateXProperty, translateYProperty, zIndexProperty, backgroundInternalProperty } from '../../styling/style-properties';
@@ -16,6 +17,7 @@ import { CoreTypes } from '../../../core-types';
import type { ModalTransition } from '../../transition/modal-transition';
import { SharedTransition } from '../../transition/shared-transition';
import { NativeScriptUIView } from '../../utils';
import { Color } from '../../../color';
export * from './view-common';
// helpers (these are okay re-exported here)
@@ -53,6 +55,12 @@ export class View extends ViewCommon implements ViewDefinition {
*/
_nativeBackgroundState: 'unset' | 'invalid' | 'drawn';
/**
* Glass effect configuration
*/
private _glassEffectView: UIVisualEffectView;
private _glassEffectMeasure: NodeJS.Timeout;
get isLayoutRequired(): boolean {
return (this._privateFlags & PFLAG_LAYOUT_REQUIRED) === PFLAG_LAYOUT_REQUIRED;
}
@@ -889,6 +897,60 @@ export class View extends ViewCommon implements ViewDefinition {
}
}
[iosGlassEffectProperty.setNative](value: GlassEffectType) {
if (!this.nativeViewProtected || !supportsGlass()) {
return;
}
if (this._glassEffectView) {
this._glassEffectView.removeFromSuperview();
this._glassEffectView = null;
}
if (!value) {
return;
}
let effect: UIGlassEffect;
if (typeof value === 'string') {
effect = UIGlassEffect.effectWithStyle(this.toUIGlassStyle(value));
} else {
if (value.variant === 'identity') {
return;
}
effect = UIGlassEffect.effectWithStyle(this.toUIGlassStyle(value.variant));
if (value.interactive) {
effect.interactive = true;
}
if (value.tint) {
effect.tintColor = typeof value.tint === 'string' ? new Color(value.tint).ios : value.tint;
}
}
this._glassEffectView = UIVisualEffectView.alloc().initWithEffect(effect);
// let touches pass to content
this._glassEffectView.userInteractionEnabled = false;
this._glassEffectView.clipsToBounds = true;
// size & autoresize
if (this._glassEffectMeasure) {
clearTimeout(this._glassEffectMeasure);
}
this._glassEffectMeasure = setTimeout(() => {
const size = this.nativeViewProtected.bounds.size;
this._glassEffectView.frame = CGRectMake(0, 0, size.width, size.height);
this._glassEffectView.autoresizingMask = 2;
this.nativeViewProtected.insertSubviewAtIndex(this._glassEffectView, 0);
});
}
public toUIGlassStyle(value?: GlassEffectVariant) {
if (supportsGlass()) {
switch (value) {
case 'regular':
return UIGlassEffectStyle?.Regular ?? 0;
case 'clear':
return UIGlassEffectStyle?.Clear ?? 1;
}
}
return 1;
}
public sendAccessibilityEvent(options: Partial<AccessibilityEventOptions>): void {
if (!isAccessibilityServiceEnabled()) {
return;

View File

@@ -1293,6 +1293,7 @@ export const isUserInteractionEnabledProperty = new Property<ViewCommon, boolean
});
isUserInteractionEnabledProperty.register(ViewCommon);
// Apple only
export const iosOverflowSafeAreaProperty = new Property<ViewCommon, boolean>({
name: 'iosOverflowSafeArea',
defaultValue: false,
@@ -1313,6 +1314,17 @@ export const iosIgnoreSafeAreaProperty = new InheritedProperty({
});
iosIgnoreSafeAreaProperty.register(ViewCommon);
/**
* Glass effects
*/
export type GlassEffectVariant = 'regular' | 'clear' | 'identity';
export type GlassEffectConfig = { variant?: GlassEffectVariant; interactive?: boolean; tint: string | Color };
export type GlassEffectType = GlassEffectVariant | GlassEffectConfig;
export const iosGlassEffectProperty = new Property<ViewCommon, GlassEffectType>({
name: 'iosGlassEffect',
});
iosGlassEffectProperty.register(ViewCommon);
export const visionHoverStyleProperty = new Property<ViewCommon, string | VisionHoverOptions>({
name: 'visionHoverStyle',
valueChanged(view, oldValue, newValue) {
@@ -1329,6 +1341,7 @@ const visionIgnoreHoverStyleProperty = new Property<ViewCommon, boolean>({
valueConverter: booleanConverter,
});
visionIgnoreHoverStyleProperty.register(ViewCommon);
// Apple only end
const touchAnimationProperty = new Property<ViewCommon, boolean | TouchAnimationOptions>({
name: 'touchAnimation',

View File

@@ -14,7 +14,7 @@ export { ControlStateChangeListener } from './core/control-state-change';
export { ViewBase, eachDescendant, getAncestor, getViewById, booleanConverter, querySelectorAll } from './core/view-base';
export type { ShowModalOptions } from './core/view-base';
export { View, CSSType, ContainerView, ViewHelper, AndroidHelper, IOSHelper, isUserInteractionEnabledProperty, PseudoClassHandler, CustomLayoutView } from './core/view';
export type { Template, KeyedTemplate, ShownModallyData, AddArrayFromBuilder, AddChildFromBuilder, Size } from './core/view';
export type { Template, KeyedTemplate, ShownModallyData, AddArrayFromBuilder, AddChildFromBuilder, Size, GlassEffectConfig, GlassEffectType, GlassEffectVariant } from './core/view';
export { Property, CoercibleProperty, InheritedProperty, CssProperty, InheritedCssProperty, ShorthandProperty, CssAnimationProperty, unsetValue, makeParser, makeValidator } from './core/properties';
export { addWeakEventListener, removeWeakEventListener } from './core/weak-event-listener';
export { DatePicker } from './date-picker';

View File

@@ -1 +1,4 @@
export const SDK_VERSION = android.os.Build.VERSION.SDK_INT;
export function supportsGlass(): boolean {
return false;
}

View File

@@ -1 +1,2 @@
export const SDK_VERSION: number;
export function supportsGlass(): boolean;

View File

@@ -1 +1,4 @@
export const SDK_VERSION = parseFloat(UIDevice.currentDevice.systemVersion);
export function supportsGlass(): boolean {
return __APPLE__ && SDK_VERSION >= 26;
}