8000 feat(core): add `sys://` support for SF Symbol usage on images with e… · NativeScript/NativeScript@d678915 · GitHub
[go: up one dir, main page]

Skip to content

Commit d678915

Browse files
authored
feat(core): add sys:// support for SF Symbol usage on images with effects (#10555)
1 parent 84e1a67 commit d678915

15 files changed

+269
-13
lines changed

apps/toolbox/src/pages/image-handling.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Observable, EventData, Page, ImageSource, knownFolders, path } from '@nativescript/core';
1+
import { Observable, EventData, Page, ImageSource, knownFolders, path, ImageSymbolEffects } from '@nativescript/core';
22
import { create, ImagePickerMediaType } from '@nativescript/imagepicker';
33

44
let page: Page;
@@ -10,6 +10,10 @@ export function navigatingTo(args: EventData) {
1010

1111
export class DemoModel extends Observable {
1212
addingPhoto = false;
13+
symbolWiggleEffect: ImageSymbolEffects.Wiggle;
14+
symbolBounceEffect: ImageSymbolEffects.Bounce;
15+
symbolBreathEffect: ImageSymbolEffects.Breathe;
16+
symbolRotateEffect: ImageSymbolEffects.Rotate;
1317

1418
pickImage() {
1519
const context = create({

apps/toolbox/src/pages/image-handling.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,27 @@
55
</Page.actionBar>
66

77
<StackLayout class="p-20">
8+
89
<Label text="Test Memory leaks with image picking and saving to device. Best to profile from platform IDE like Xcode." textWrap="true" />
910

1011
<Button text="Pick and Save Image" tap="{{ pickImage }}" />
1112

13+
<ios>
14+
<!-- SF Symbols with Effects -->
15+
<ContentView height="1" width="100%" backgroundColor="#efefef" margin="10"></ContentView>
16+
<GridLayout rows="auto,auto,auto" columns="*,*">
17+
<Image src="sys://photo.on.rectangle.angled" width="100" tintColor="green" symbolEffect="{{symbolWiggleEffect}}" padding="8"/>
18+
<Image col="1" src="sys://steeringwheel.and.hands" width="100" tintColor="black" symbolEffect="{{symbolWiggleEffect}}" padding="8" />
19+
20+
<Image row="1" src="sys://airpods.pro.chargingcase.wireless.radiowaves.left.and.right.fill" width="100" symbolEffect="{{symbolBounceEffect}}" padding="8" />
21+
<Image row="1" col="1" src="sys://lungs.fill" width="100" symbolEffect="{{symbolBreathEffect}}" padding="8" />
22+
23+
24+
<Image row="2" src="sys://clock.arrow.trianglehead.2.counterclockwise.rotate.90" width="100" symbolEffect="{{symbolRotateEffect}}" padding="8" />
25+
<Image row="2" col="1" src="sys://square.and.arrow.up" width="100" symbolEffect="{{symbolWiggleEffect}}" padding="8" />
26+
</GridLayout>
27+
</ios>
28+
29+
1230
</StackLayout>
1331
</Page>

packages/core/image-source/index.android.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,14 @@ export class ImageSource implements ImageSourceDefinition {
149149
return ImageSource.fromFileSync(path);
150150
}
151151

152+
static fromSystemImageSync(name: string): ImageSource {
153+
return ImageSource.fromResourceSync(name);
154+
}
155+
156+
static fromSystemImage(name: string): Promise<ImageSource> {
157+
return ImageSource.fromResource(name);
158+
}
159+
152160
static fromDataSync(data: any): ImageSource {
153161
const bitmap = android.graphics.BitmapFactory.decodeStream(data);
154162

@@ -335,7 +343,7 @@ export class ImageSource implements ImageSourceDefinition {
335343
reject();
336344
}
337345
},
338-
})
346+
}),
339347
);
340348
});
341349
}
@@ -375,7 +383,7 @@ export class ImageSource implements ImageSourceDefinition {
375383
reject();
376384
}
377385
},
378-
})
386+
}),
379387
);
380388
});
381389
}
@@ -404,7 +412,7 @@ export class ImageSource implements ImageSourceDefinition {
404412
reject();
405413
}
406414
},
407-
})
415+
}),
408416
);
409417
});
410418
}

packages/core/image-source/index.d.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ export class ImageSource {
5454
*/
5555
static fromResource(name: string): Promise<ImageSource>;
5656

57+
/**
58+
* Loads this instance from the specified system image name.
59+
* @param name the name of the system image
60+
*/
61+
static fromSystemImageSync(name: string): ImageSource;
62+
63+
/**
64+
* Loads this instance from the specified system image name asynchronously.
65+
* @param name the name of the system image
66+
*/
67+
static fromSystemImage(name: string): Promise<ImageSource>;
68+
5769
/**
5870
* Loads this instance from the specified file.
5971
* @param path The location of the file on the file system.

packages/core/image-source/index.ios.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Trace } from '../trace';
88

99
// Types.
1010
import { path as fsPath, knownFolders } from '../file-system';
11-
import { isFileOrResourcePath, RESOURCE_PREFIX, layout, releaseNativeObject } from '../utils';
11+
import { isFileOrResourcePath, RESOURCE_PREFIX, layout, releaseNativeObject, SYSTEM_PREFIX } from '../utils';
1212

1313
import { getScaledDimensions } from './image-source-common';
1414

@@ -73,6 +73,27 @@ export class ImageSource implements ImageSourceDefinition {
7373
return http.getImage(url);
7474
}
7575

76+
static fromSystemImageSync(name: string): ImageSource {
77+
const image = UIImage.systemImageNamed(name);
78+
79+
return image ? new ImageSource(image) : null;
80+
}
81+
82+
static fromSystemImage(name: string): Promise<ImageSource> {
83+
return new Promise<ImageSource>((resolve, reject) => {
84+
try {
85+
const image = UIImage.systemImageNamed(name);
86+
if (image) {
87+
resolve(new ImageSource(image));
88+
} else {
89+
reject(new Error(`Failed to load system icon with name: ${name}`));
90+
}
91+
} catch (ex) {
92+
reject(ex);
93+
}
94+
});
95+
}
96+
7697
static fromResourceSync(name: string): ImageSource {
7798
const nativeSource = (<any>UIImage).tns_safeImageNamed(name) || (<any>UIImage).tns_safeImageNamed(`${name}.jpg`);
7899

@@ -126,7 +147,10 @@ export class ImageSource implements ImageSourceDefinition {
126147
}
127148

128149
if (path.indexOf(RESOURCE_PREFIX) === 0) {
129-
return ImageSource.fromResourceSync(path.substr(RESOURCE_PREFIX.length));
150+
return ImageSource.fromResourceSync(path.slice(RESOURCE_PREFIX.length));
151+
}
152+
if (path.indexOf(SYSTEM_PREFIX) === 0) {
153+
return ImageSource.fromSystemImageSync(path.slice(SYSTEM_PREFIX.length));
130154
}
131155

132156
return ImageSource.fromFileSync(path);

packages/core/references.d.ts

Lines changed: 1 addition & 0 deletions
@@ -2,6 +2,7 @@
Original file line numberDiff line numberDiff line change
22
/// <reference path="../types-ios/src/lib/ios/objc-x86_64/objc!CFNetwork.d.ts" />
33
/// <reference path="../types-ios/src/lib/ios/objc-x86_64/objc!CoreText.d.ts" />
44
/// <reference path="../types-ios/src/lib/ios/objc-x86_64/objc!Darwin.d.ts" />
5+
/// <reference path="../types-ios/src/lib/ios/objc-x86_64/objc!Symbols.d.ts" />
56
/// <reference path="../types-android/src/lib/android-29.d.ts" />
67
/// <reference path="./platforms/ios/typings/objc!MaterialComponents.d.ts" />
78
/// <reference path="./platforms/ios/typings/objc!NativeScriptUtils.d.ts" />

packages/core/ui/image/image-common.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import { booleanConverter } from '../core/view-base';
44
import { CoreTypes } from '../../core-types';
55
import { ImageAsset } from '../../image-asset';
66
import { ImageSource } from '../../image-source';
7-
import { isDataURI, isFontIconURI, isFileOrResourcePath, RESOURCE_PREFIX } from '../../utils';
7+
import { isDataURI, isFontIconURI, isFileOrResourcePath, RESOURCE_PREFIX, SYSTEM_PREFIX } from '../../utils';
88
import { Color } from '../../color';
99
import { Style } from '../styling/style';
1010
import { Length } from '../styling/style-properties';
1111
import { Property, InheritedCssProperty } from '../core/properties';
1212
import { Trace } from '../../trace';
13+
import { ImageSymbolEffect, ImageSymbolEffects } from './symbol-effects';
1314

1415
@CSSType('Image')
1516
export abstract class ImageBase extends View implements ImageDefinition {
@@ -75,13 +76,21 @@ export abstract class ImageBase extends View implements ImageDefinition {
7576
}
7677
} else if (isFileOrResourcePath(value)) {
7778
if (value.indexOf(RESOURCE_PREFIX) === 0) {
78-
const resPath = value.substr(RESOURCE_PREFIX.length);
79+
const resPath = value.slice(RESOURCE_PREFIX.length);
7980
if (sync) {
8081
imageLoaded(ImageSource.fromResourceSync(resPath));
8182
} else {
8283
this.imageSource = null;
8384
ImageSource.fromResource(resPath).then(imageLoaded);
8485
}
86+
} else if (value.indexOf(SYSTEM_PREFIX) === 0) {
87+
const sysPath = value.slice(SYSTEM_PREFIX.length);
88+
if (sync) {
89+
imageLoaded(ImageSource.fromSystemImageSync(sysPath));
90+
} else {
91+
this.imageSource = null;
92+
ImageSource.fromSystemImage(sysPath).then(imageLoaded);
93+
}
8594
} else {
8695
if (sync) {
8796
imageLoaded(ImageSource.fromFileSync(value));
@@ -178,3 +187,13 @@ export const decodeWidthProperty = new Property<ImageBase, CoreTypes.LengthType>
178187
valueConverter: Length.parse,
179188
});
180189
decodeWidthProperty.register(ImageBase);
190+
191+
/**
192+
* iOS only
193+
*/
194+
export const iosSymbolEffectProperty = new Property<ImageBase, ImageSymbolEffect | ImageSymbolEffects>({
195+
name: 'iosSymbolEffect',
196+
});
197+
iosSymbolEffectProperty.register(ImageBase);
198+
199+
export { ImageSymbolEffect, ImageSymbolEffects };

packages/core/ui/image/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Color } from '../../color';
66
import { Property, InheritedCssProperty } from '../core/properties';
77
import { CoreTypes } from '../../core-types';
88

9+
export { ImageSymbolEffect, ImageSymbolEffects } from './image-common';
910
/**
1011
* Represents a class that provides functionality for loading and streching image(s).
1112
*/

packages/core/ui/image/index.ios.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { ImageBase, stretchProperty, imageSourceProperty, tintColorProperty, srcProperty } from './image-common';
1+
import { ImageBase, stretchProperty, im 10000 ageSourceProperty, tintColorProperty, srcProperty, iosSymbolEffectProperty, ImageSymbolEffect, ImageSymbolEffects } from './image-common';
22
import { ImageSource } from '../../image-source';
33
import { ImageAsset } from '../../image-asset';
44
import { Color } from '../../color';
55
import { Trace } from '../../trace';
66
import { layout, queueGC } from '../../utils';
7+
import { SDK_VERSION } from '../../utils/constants';
78

89
export * from './image-common';
910

@@ -194,4 +195,16 @@ export class Image extends ImageBase {
194195
[srcProperty.setNative](value: string | ImageSource | ImageAsset) {
195196
this._createImageSourceFromSrc(value);
196197
}
198+
199+
[iosSymbolEffectProperty.setNative](value: ImageSymbolEffect | ImageSymbolEffects) {
200+
if (SDK_VERSION < 17) {
201+
return;
202+
}
203+
const symbol = typeof value === 'string' ? ImageSymbolEffect.fromSymbol(value) : value;
204+
if (symbol && symbol.effect) {
205+
this.nativeViewProtected.addSymbolEffectOptionsAnimatedCompletion(symbol.effect, symbol.options || NSSymbolEffectOptions.optionsWithRepeating(), true, symbol.completion || null);
206+
} else {
207+
this.nativeViewProtected.removeAllSymbolEffects();
208+
}
209+
}
197210
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
export enum ImageSymbolEffects {
2+
Appear = 'appear',
3+
AppearUp = 'appearUp',
4+
AppearDown = 'appearDown',
5+
Bounce = 'bounce',
6+
BounceUp = 'bounceUp',
7+
BounceDown = 'bounceDown',
8+
Disappear = 'disappear',
9+
DisappearDown = 'disappearDown',
10+
DisappearUp = 'disappearUp',
11+
Pulse = 'pulse',
12+
Scale = 'scale',
13+
ScaleDown = 'scaleDown',
14+
ScaleUp = 'scaleUp',
15+
VariableColor = 'variableColor',
16+
Breathe = 'breathe',
17+
BreathePlain = 'breathePlain',
18+
BreathePulse = 'breathePulse',
19+
Rotate = 'rotate',
20+
RotateClockwise = 'rotateClockwise',
21+
RotateCounterClockwise = 'rotateCounterClockwise',
22+
Wiggle = 'wiggle',
23+
WiggleBackward = 'wiggleBackward',
24+
WiggleClockwise = 'wiggleClockwise',
25+
WiggleCounterClockwise = 'wiggleCounterClockwise',
26+
WiggleDown = 'wiggleDown',
27+
WiggleForward = 'wiggleForward',
28+
WiggleUp = 'wiggleUp',
29+
WiggleLeft = 'wiggleLeft',
30+
WiggleRight = 'wiggleRight',
31+
}
32+
33+
export class ImageSymbolEffectCommon {
34+
effect?: NSSymbolEffect;
35+
options?: NSSymbolEffectOptions;
36+
completion?: (context: UISymbolEffectCompletionContext) => void;
37+
}

0 commit comments

Comments
 (0)
0