diff --git a/apps/toolbox/src/pages/image-handling.ts b/apps/toolbox/src/pages/image-handling.ts index 563498f1fa..5feea4f469 100644 --- a/apps/toolbox/src/pages/image-handling.ts +++ b/apps/toolbox/src/pages/image-handling.ts @@ -10,10 +10,10 @@ export function navigatingTo(args: EventData) { export class DemoModel extends Observable { addingPhoto = false; - symbolWiggleEffect: ImageSymbolEffects.Wiggle; - symbolBounceEffect: ImageSymbolEffects.Bounce; - symbolBreathEffect: ImageSymbolEffects.Breathe; - symbolRotateEffect: ImageSymbolEffects.Rotate; + symbolWiggleEffect = ImageSymbolEffects.Wiggle; + symbolBounceEffect = ImageSymbolEffects.Bounce; + symbolBreathEffect = ImageSymbolEffects.Breathe; + symbolRotateEffect = ImageSymbolEffects.Rotate; pickImage() { const context = create({ diff --git a/apps/toolbox/src/pages/image-handling.xml b/apps/toolbox/src/pages/image-handling.xml index ad735e19b8..a7022da8fe 100644 --- a/apps/toolbox/src/pages/image-handling.xml +++ b/apps/toolbox/src/pages/image-handling.xml @@ -13,16 +13,23 @@ - - - + + + - - + + + + + + + + + + + - - diff --git a/packages/core/image-source/index.android.ts b/packages/core/image-source/index.android.ts index ca36e9f4bd..d4836484e9 100644 --- a/packages/core/image-source/index.android.ts +++ b/packages/core/image-source/index.android.ts @@ -1,5 +1,5 @@ // Definitions. -import { ImageSource as ImageSourceDefinition } from '.'; +import { ImageSource as ImageSourceDefinition, iosSymbolScaleType } from '.'; import { ImageAsset } from '../image-asset'; import * as httpModule from '../http'; @@ -149,6 +149,10 @@ export class ImageSource implements ImageSourceDefinition { return ImageSource.fromFileSync(path); } + static iosSymbolScaleFor(scale: iosSymbolScaleType): number { + return 0; + } + static fromSystemImageSync(name: string): ImageSource { return ImageSource.fromResourceSync(name); } diff --git a/packages/core/image-source/index.d.ts b/packages/core/image-source/index.d.ts index 57e533595e..963c993b4a 100644 --- a/packages/core/image-source/index.d.ts +++ b/packages/core/image-source/index.d.ts @@ -54,17 +54,23 @@ export class ImageSource { */ static fromResource(name: string): Promise; + /** + * (iOS only) Get system symbol scale + * @param scale symbol scale type + */ + static iosSymbolScaleFor(scale: iosSymbolScaleType): number; + /** * Loads this instance from the specified system image name. * @param name the name of the system image */ - static fromSystemImageSync(name: string): ImageSource; + static fromSystemImageSync(name: string, scale?: iosSymbolScaleType): ImageSource; /** * Loads this instance from the specified system image name asynchronously. * @param name the name of the system image */ - static fromSystemImage(name: string): Promise; + static fromSystemImage(name: string, scale?: iosSymbolScaleType): Promise; /** * Loads this instance from the specified file. @@ -259,6 +265,12 @@ export class ImageSource { resizeAsync(maxSize: number, options?: any): Promise; } +/** + * iOS only + * SF Symbol scale + */ +export type iosSymbolScaleType = 'default' | 'small' | 'medium' | 'large'; + /** * @deprecated Use ImageSource.fromAsset() instead. * Creates a new ImageSource instance and loads it from the specified image asset asynchronously. diff --git a/packages/core/image-source/index.ios.ts b/packages/core/image-source/index.ios.ts index f6d854ec8a..b47414a78d 100644 --- a/packages/core/image-source/index.ios.ts +++ b/packages/core/image-source/index.ios.ts @@ -1,5 +1,5 @@ // Definitions. -import { ImageSource as ImageSourceDefinition } from '.'; +import { ImageSource as ImageSourceDefinition, iosSymbolScaleType } from '.'; import { ImageAsset } from '../image-asset'; import * as httpModule from '../http'; import { Font } from '../ui/styling/font'; @@ -73,16 +73,39 @@ export class ImageSource implements ImageSourceDefinition { return http.getImage(url); } - static fromSystemImageSync(name: string): ImageSource { - const image = UIImage.systemImageNamed(name); + static iosSystemScaleFor(scale: iosSymbolScaleType) { + switch (scale) { + case 'small': + return UIImageSymbolScale.Small; + case 'medium': + return UIImageSymbolScale.Medium; + case 'large': + return UIImageSymbolScale.Large; + default: + return UIImageSymbolScale.Default; + } + } + + static fromSystemImageSync(name: string, scale?: iosSymbolScaleType): ImageSource { + if (scale) { + const image = UIImage.systemImageNamedWithConfiguration(name, UIImageSymbolConfiguration.configurationWithScale(ImageSource.iosSystemScaleFor(scale))); + return image ? new ImageSource(image) : null; + } else { + const image = UIImage.systemImageNamed(name); - return image ? new ImageSource(image) : null; + return image ? new ImageSource(image) : null; + } } - static fromSystemImage(name: string): Promise { + static fromSystemImage(name: string, scale?: iosSymbolScaleType): Promise { return new Promise((resolve, reject) => { try { - const image = UIImage.systemImageNamed(name); + let image: UIImage; + if (scale) { + image = UIImage.systemImageNamedWithConfiguration(name, UIImageSymbolConfiguration.configurationWithScale(ImageSource.iosSystemScaleFor(scale))); + } else { + image = UIImage.systemImageNamed(name); + } if (image) { resolve(new ImageSource(image)); } else { diff --git a/packages/core/references.d.ts b/packages/core/references.d.ts index 93bd89d8d6..e55d0d3bdf 100644 --- a/packages/core/references.d.ts +++ b/packages/core/references.d.ts @@ -2,6 +2,7 @@ /// /// /// +/// /// /// /// diff --git a/packages/core/ui/core/view/index.ios.ts b/packages/core/ui/core/view/index.ios.ts index df4d10460d..447ee251ca 100644 --- a/packages/core/ui/core/view/index.ios.ts +++ b/packages/core/ui/core/view/index.ios.ts @@ -901,8 +901,8 @@ export class View extends ViewCommon implements ViewDefinition { let notification: number; let args: string | UIView | null = this.nativeViewProtected; - if (typeof msg === 'string' && msg) { - args = msg; + if (options?.message) { + args = options.message; } switch (options.iosNotificationType) { diff --git a/packages/core/ui/image/image-common.ts b/packages/core/ui/image/image-common.ts index d430ac3a6a..e945665dd3 100644 --- a/packages/core/ui/image/image-common.ts +++ b/packages/core/ui/image/image-common.ts @@ -3,7 +3,7 @@ import { View, CSSType } from '../core/view'; import { booleanConverter } from '../core/view-base'; import { CoreTypes } from '../../core-types'; import { ImageAsset } from '../../image-asset'; -import { ImageSource } from '../../image-source'; +import { ImageSource, iosSymbolScaleType } from '../../image-source'; import { isDataURI, isFontIconURI, isFileOrResourcePath, RESOURCE_PREFIX, SYSTEM_PREFIX } from '../../utils'; import { Color } from '../../color'; import { Style } from '../styling/style'; @@ -21,6 +21,7 @@ export abstract class ImageBase extends View implements ImageDefinition { public loadMode: 'sync' | 'async'; public decodeWidth: CoreTypes.LengthType; public decodeHeight: CoreTypes.LengthType; + public iosSymbolScale: iosSymbolScaleType; get tintColor(): Color { return this.style.tintColor; @@ -86,10 +87,10 @@ export abstract class ImageBase extends View implements ImageDefinition { } else if (value.indexOf(SYSTEM_PREFIX) === 0) { const sysPath = value.slice(SYSTEM_PREFIX.length); if (sync) { - imageLoaded(ImageSource.fromSystemImageSync(sysPath)); + imageLoaded(ImageSource.fromSystemImageSync(sysPath, this.iosSymbolScale)); } else { this.imageSource = null; - ImageSource.fromSystemImage(sysPath).then(imageLoaded); + ImageSource.fromSystemImage(sysPath, this.iosSymbolScale).then(imageLoaded); } } else { if (sync) { @@ -196,4 +197,12 @@ export const iosSymbolEffectProperty = new Property({ + name: 'iosSymbolScale', +}); +iosSymbolScaleProperty.register(ImageBase); + export { ImageSymbolEffect, ImageSymbolEffects }; diff --git a/packages/core/ui/image/index.ios.ts b/packages/core/ui/image/index.ios.ts index cf3d0d179c..7f82114a54 100644 --- a/packages/core/ui/image/index.ios.ts +++ b/packages/core/ui/image/index.ios.ts @@ -1,5 +1,5 @@ -import { ImageBase, stretchProperty, imageSourceProperty, tintColorProperty, srcProperty, iosSymbolEffectProperty, ImageSymbolEffect, ImageSymbolEffects } from './image-common'; -import { ImageSource } from '../../image-source'; +import { ImageBase, stretchProperty, imageSourceProperty, tintColorProperty, srcProperty, iosSymbolEffectProperty, ImageSymbolEffect, ImageSymbolEffects, iosSymbolScaleProperty } from './image-common'; +import { ImageSource, iosSymbolScaleType } from '../../image-source'; import { ImageAsset } from '../../image-asset'; import { Color } from '../../color'; import { Trace } from '../../trace'; @@ -192,8 +192,17 @@ export class Image extends ImageBase { this._setNativeImage(value ? value.ios : null); } - [srcProperty.setNative](value: string | ImageSource | ImageAsset) { + private _setSrc(value: string | ImageSource | ImageAsset) { this._createImageSourceFromSrc(value); + if (this.iosSymbolScale) { + // when applying symbol scale, contentMode must be center + // https://stackoverflow.com/a/65787627 + this.nativeViewProtected.contentMode = UIViewContentMode.Center; + } + } + + [srcProperty.setNative](value: string | ImageSource | ImageAsset) { + this._setSrc(value); } [iosSymbolEffectProperty.setNative](value: ImageSymbolEffect | ImageSymbolEffects) { @@ -201,10 +210,17 @@ export class Image extends ImageBase { return; } const symbol = typeof value === 'string' ? ImageSymbolEffect.fromSymbol(value) : value; - if (symbol && symbol.effect) { + if (symbol?.effect) { + // Note: https://developer.apple.com/documentation/symbols/symboleffectoptions/4197883-repeating + // Will want to move to https://developer.apple.com/documentation/symbols/nssymboleffectoptionsrepeatbehavior?language=objc as fallback once optionsWithRepeating is removed this.nativeViewProtected.addSymbolEffectOptionsAnimatedCompletion(symbol.effect, symbol.options || NSSymbolEffectOptions.optionsWithRepeating(), true, symbol.completion || null); } else { this.nativeViewProtected.removeAllSymbolEffects(); } } + + [iosSymbolScaleProperty.setNative](value: iosSymbolScaleType) { + // reset src to configure scale + this._setSrc(this.src); + } } diff --git a/packages/core/ui/image/symbol-effects.android.ts b/packages/core/ui/image/symbol-effects.android.ts index 27eff8434d..bd02f2736c 100644 --- a/packages/core/ui/image/symbol-effects.android.ts +++ b/packages/core/ui/image/symbol-effects.android.ts @@ -1,9 +1,8 @@ -import { ImageSymbolEffectCommon, ImageSymbolEffects } from './symbol-effects-common'; -import type { ImageSymbolEffect as ImageSymbolEffectDefinition } from './symbol-effects.d.ts'; -export { ImageSymbolEffects }; +import { ImageSymbolEffectCommon } from './symbol-effects-common'; +export { ImageSymbolEffects } from './symbol-effects-common'; -export const ImageSymbolEffect: typeof ImageSymbolEffectDefinition = class ImageSymbolEffect extends ImageSymbolEffectCommon implements ImageSymbolEffectDefinition { - static fromSymbol(symbol: string): ImageSymbolEffectDefinition { +export class ImageSymbolEffect extends ImageSymbolEffectCommon { + static fromSymbol(symbol: string) { return new ImageSymbolEffect(); } -}; +} diff --git a/packages/core/ui/image/symbol-effects.ios.ts b/packages/core/ui/image/symbol-effects.ios.ts index 0ae1bfee7b..dbf937475f 100644 --- a/packages/core/ui/image/symbol-effects.ios.ts +++ b/packages/core/ui/image/symbol-effects.ios.ts @@ -1,13 +1,13 @@ import { SDK_VERSION } from '../../utils/constants'; import { ImageSymbolEffectCommon, ImageSymbolEffects } from './symbol-effects-common'; -import type { ImageSymbolEffect as ImageSymbolEffectDefinition } from './symbol-effects.d.ts'; +export { ImageSymbolEffects } from './symbol-effects-common'; -export const ImageSymbolEffect: typeof ImageSymbolEffectDefinition = class ImageSymbolEffect extends ImageSymbolEffectCommon implements ImageSymbolEffectDefinition { +export class ImageSymbolEffect extends ImageSymbolEffectCommon { constructor(symbol: NSSymbolEffect) { super(); this.effect = symbol; } - static fromSymbol(symbol: string): ImageSymbolEffectDefinition | null { + static fromSymbol(symbol: string): ImageSymbolEffect | null { if (SDK_VERSION < 17) { return null; } @@ -44,52 +44,37 @@ export const ImageSymbolEffect: typeof ImageSymbolEffectDefinition = class Image if (SDK_VERSION < 18) { return null; } - // TODO: remove ts-expect-error once we bump the types package switch (symbol) { case ImageSymbolEffects.Breathe: - // @ts-expect-error added on iOS 18 return new ImageSymbolEffect(NSSymbolBreatheEffect.effect()); case ImageSymbolEffects.BreathePlain: - // @ts-expect-error added on iOS 18 return new ImageSymbolEffect(NSSymbolBreatheEffect.breathePlainEffect()); case ImageSymbolEffects.Rotate: - // @ts-expect-error added on iOS 18 return new ImageSymbolEffect(NSSymbolRotateEffect.effect()); case ImageSymbolEffects.RotateClockwise: - // @ts-expect-error added on iOS 18 return new ImageSymbolEffect(NSSymbolRotateEffect.rotateClockwiseEffect()); case ImageSymbolEffects.RotateCounterClockwise: - // @ts-expect-error added on iOS 18 return new ImageSymbolEffect(NSSymbolRotateEffect.rotateCounterClockwiseEffect()); case ImageSymbolEffects.Wiggle: - // @ts-expect-error added on iOS 18 return new ImageSymbolEffect(NSSymbolWiggleEffect.effect()); case ImageSymbolEffects.WiggleBackward: - // @ts-expect-error added on iOS 18 return new ImageSymbolEffect(NSSymbolWiggleEffect.wiggleBackwardEffect()); case ImageSymbolEffects.WiggleClockwise: - // @ts-expect-error added on iOS 18 return new ImageSymbolEffect(NSSymbolWiggleEffect.wiggleClockwiseEffect()); case ImageSymbolEffects.WiggleCounterClockwise: - // @ts-expect-error added on iOS 18 return new ImageSymbolEffect(NSSymbolWiggleEffect.wiggleCounterClockwiseEffect()); case ImageSymbolEffects.WiggleDown: - // @ts-expect-error added on iOS 18 return new ImageSymbolEffect(NSSymbolWiggleEffect.wiggleDownEffect()); case ImageSymbolEffects.WiggleForward: - // @ts-expect-error added on iOS 18 return new ImageSymbolEffect(NSSymbolWiggleEffect.wiggleForwardEffect()); case ImageSymbolEffects.WiggleUp: - // @ts-expect-error added on iOS 18 return new ImageSymbolEffect(NSSymbolWiggleEffect.wiggleUpEffect()); case ImageSymbolEffects.WiggleLeft: - // @ts-expect-error added on iOS 18 return new ImageSymbolEffect(NSSymbolWiggleEffect.wiggleLeftEffect()); case ImageSymbolEffects.WiggleRight: - // @ts-expect-error added on iOS 18 return new ImageSymbolEffect(NSSymbolWiggleEffect.wiggleRightEffect()); } return null; } -}; +} diff --git a/packages/types-ios/src/lib/ios/objc-x86_64/objc!NativeScriptEmbedder.d.ts b/packages/types-ios/src/lib/ios/objc-x86_64/objc!NativeScriptEmbedder.d.ts index c5febdb8b5..8a82a60a76 100644 --- a/packages/types-ios/src/lib/ios/objc-x86_64/objc!NativeScriptEmbedder.d.ts +++ b/packages/types-ios/src/lib/ios/objc-x86_64/objc!NativeScriptEmbedder.d.ts @@ -28,3 +28,11 @@ declare var NativeScriptEmbedderDelegate: { prototype: NativeScriptEmbedderDelegate; }; + +declare class NativeScriptViewFactory extends NSObject { + static getKeyWindow(): UIWindow; + static shared: NativeScriptViewFactory; + views: NSMutableDictionary; + viewCreator: (id: string, ctrl: UIViewController) => void; + viewDestroyer: (id: string) => void; +} \ No newline at end of file