Skip to content

Commit e853fca

Browse files
authored
feat(ios): apple intelligence writing tools (#10643)
1 parent 1b72912 commit e853fca

File tree

17 files changed

+635
-65
lines changed

17 files changed

+635
-65
lines changed

apps/automated/project.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"executor": "@nativescript/nx:debug",
2525
"options": {
2626
"noHmr": true,
27+
"debug": false,
2728
"uglify": false,
2829
"release": false,
2930
"forDevice": false,

apps/toolbox/src/pages/forms.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Page, Observable, EventData, TextField, PropertyChangeData } from '@nativescript/core';
1+
import { Page, Observable, EventData, TextField, PropertyChangeData, TextView } from '@nativescript/core';
22

33
let page: Page;
44

@@ -28,6 +28,12 @@ export class SampleData extends Observable {
2828
console.log(args.value);
2929
this.notifyPropertyChange('formattedPhoneInput', args.value);
3030
}
31+
32+
textChangeArea(args: PropertyChangeData) {
33+
const textArea = args.object as TextView;
34+
console.log('---- AI active:', textArea.isWritingToolsActive);
35+
console.log('textChangeArea:', args.value);
36+
}
3137
}
3238

3339
function formatPhoneNumber(value: string, useParens?: boolean) {

apps/toolbox/src/pages/forms.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
</TextField>
2121
<Label text="{{ formattedPhoneInput }}" marginTop="6" />
2222

23+
<Label text="TextView" fontWeight="bold" marginTop="12" />
24+
<TextView hint="Type text..." style.placeholderColor="silver" textChange="{{textChangeArea}}" color="black" width="80%" borderColor="silver" borderWidth="1" height="200" borderRadius="4" fontSize="14">
25+
</TextView>
26+
2327
</StackLayout>
2428
</ScrollView>
2529
</Page>

packages/core/ui/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export { TextBase, getTransformedText, letterSpacingProperty, textAlignmentPrope
7575
export { FormattedString } from './text-base/formatted-string';
7676
export { Span } from './text-base/span';
7777
export { TextField } from './text-field';
78-
export { TextView } from './text-view';
78+
export { TextView, WritingToolsAllowedInput, WritingToolsBehavior } from './text-view';
7979
export { TimePicker } from './time-picker';
8080
export { Transition } from './transition';
8181
export { ModalTransition } from './transition/modal-transition';

packages/core/ui/text-view/index.android.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { TextViewBase as TextViewBaseCommon } from './text-view-common';
22
import { CSSType } from '../core/view';
3-
3+
export { WritingToolsAllowedInput, WritingToolsBehavior } from './text-view-common';
44
export * from '../text-base';
55

66
@CSSType('TextView')

packages/core/ui/text-view/index.d.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { EditableTextBase } from '../editable-text-base';
22
import { Property } from '../core/properties';
3+
export { WritingToolsAllowedInput, WritingToolsBehavior } from './text-view-common';
34

45
/**
56
* Represents an editable multiline text view.
@@ -30,6 +31,16 @@ export class TextView extends EditableTextBase {
3031
* @nsProperty
3132
*/
3233
maxLines: number;
34+
35+
/**
36+
* (iOS Only) Are Apple Intelligence writing tools active
37+
*/
38+
isWritingToolsActive: boolean;
39+
40+
/**
41+
* (iOS Only) Allow Apple Intelligence writing tools to emit text changes on each alteration instead of after the final change (default).
42+
*/
43+
enableWritingToolsEvents: boolean;
3344
}
3445

3546
export const maxLinesProperty: Property<EditableTextBase, number>;

packages/core/ui/text-view/index.ios.ts

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { ScrollEventData } from '../scroll-view';
22
import { textProperty } from '../text-base';
3-
import { TextViewBase as TextViewBaseCommon } from './text-view-common';
3+
import { iosWritingToolsAllowedInputProperty, iosWritingToolsBehaviorProperty, TextViewBase as TextViewBaseCommon, WritingToolsAllowedInput, WritingToolsBehavior } from './text-view-common';
44
import { editableProperty, hintProperty, placeholderColorProperty, _updateCharactersInRangeReplacementString } from '../editable-text-base';
55
import { CoreTypes } from '../../core-types';
66
import { CSSType } from '../core/view';
77
import { Color } from '../../color';
88
import { colorProperty, borderTopWidthProperty, borderRightWidthProperty, borderBottomWidthProperty, borderLeftWidthProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, paddingLeftProperty, Length } from '../styling/style-properties';
9-
import { iOSNativeHelper, layout } from '../../utils';
9+
import { layout, isRealDevice } from '../../utils';
10+
import { SDK_VERSION } from '../../utils/constants';
1011

1112
import { profile } from '../../profiling';
12-
13-
const majorVersion = iOSNativeHelper.MajorVersion;
13+
export { WritingToolsAllowedInput, WritingToolsBehavior } from './text-view-common';
1414

1515
@NativeClass
1616
class UITextViewDelegateImpl extends NSObject implements UITextViewDelegate {
@@ -70,6 +70,21 @@ class UITextViewDelegateImpl extends NSObject implements UITextViewDelegate {
7070
return owner.scrollViewDidScroll(sv);
7171
}
7272
}
73+
74+
public textViewWritingToolsWillBegin(textView: UITextView): void {
75+
const owner = this._owner?.deref();
76+
if (owner) {
77+
owner.isWritingToolsActive = true;
78+
}
79+
}
80+
81+
public textViewWritingToolsDidEnd(textView: UITextView): void {
82+
const owner = this._owner?.deref();
83+
if (owner) {
84+
owner.isWritingToolsActive = false;
85+
owner.textViewDidChange(textView);
86+
}
87+
}
7388
}
7489

7590
@NativeClass
@@ -92,8 +107,8 @@ export class TextView extends TextViewBaseCommon {
92107

93108
public _isEditing: boolean;
94109

95-
private _hintColor = majorVersion <= 12 || !UIColor.placeholderTextColor ? UIColor.blackColor.colorWithAlphaComponent(0.22) : UIColor.placeholderTextColor;
96-
private _textColor = majorVersion <= 12 || !UIColor.labelColor ? null : UIColor.labelColor;
110+
private _hintColor = SDK_VERSION <= 12 || !UIColor.placeholderTextColor ? UIColor.blackColor.colorWithAlphaComponent(0.22) : UIColor.placeholderTextColor;
111+
private _textColor = SDK_VERSION <= 12 || !UIColor.labelColor ? null : UIColor.labelColor;
97112

98113
createNativeView() {
99114
const textView = NoScrollAnimationUITextView.new();
@@ -144,10 +159,12 @@ export class TextView extends TextViewBaseCommon {
144159
}
145160

146161
public textViewDidChange(textView: UITextView): void {
147-
if (this.updateTextTrigger === 'textChanged') {
148-
textProperty.nativeValueChange(this, textView.text);
162+
if (!this.isWritingToolsActive || this.enableWritingToolsEvents) {
163+
if (this.updateTextTrigger === 'textChanged') {
164+
textProperty.nativeValueChange(this, textView.text);
165+
}
166+
this.requestLayout();
149167
}
150-
this.requestLayout();
151168
}
152169

153170
public textViewShouldChangeTextInRangeReplacementText(textView: UITextView, range: NSRange, replacementString: string): boolean {
@@ -399,6 +416,52 @@ export class TextView extends TextViewBaseCommon {
399416
right: inset.right,
400417
};
401418
}
419+
420+
[iosWritingToolsBehaviorProperty.setNative](value: WritingToolsBehavior) {
421+
if (SDK_VERSION >= 18 && isRealDevice()) {
422+
this.nativeTextViewProtected.writingToolsBehavior = this._writingToolsBehaviorType(value);
423+
}
424+
}
425+
426+
[iosWritingToolsAllowedInputProperty.setNative](value: Array<WritingToolsAllowedInput>) {
427+
if (SDK_VERSION >= 18 && isRealDevice()) {
428+
let writingToolsInput = null;
429+
for (const inputType of value) {
430+
writingToolsInput = (writingToolsInput != null ? writingToolsInput : 0) + this._writingToolsAllowedType(inputType);
431+
}
432+
if (writingToolsInput === null) {
433+
writingToolsInput = UIWritingToolsResultOptions.Default;
434+
}
435+
this.nativeTextViewProtected.allowsEditingTextAttributes = true;
436+
this.nativeTextViewProtected.allowedWritingToolsResultOptions = writingToolsInput;
437+
}
438+
}
439+
440+
private _writingToolsBehaviorType(value: WritingToolsBehavior) {
441+
switch (value) {
442+
case WritingToolsBehavior.Complete:
443+
return UIWritingToolsBehavior.Complete;
444+
case WritingToolsBehavior.Default:
445+
return UIWritingToolsBehavior.Default;
446+
case WritingToolsBehavior.Limited:
447+
return UIWritingToolsBehavior.Limited;
448+
case WritingToolsBehavior.None:
449+
return UIWritingToolsBehavior.None;
450+
}
451+
}
452+
453+
private _writingToolsAllowedType(value: WritingToolsAllowedInput) {
454+
switch (value) {
455+
case WritingToolsAllowedInput.Default:
456+
return UIWritingToolsResultOptions.Default;
457+
case WritingToolsAllowedInput.List:
458+
return UIWritingToolsResultOptions.List;
459+
case WritingToolsAllowedInput.PlainText:
460+
return UIWritingToolsResultOptions.PlainText;
461+
case WritingToolsAllowedInput.RichText:
462+
return UIWritingToolsResultOptions.RichText;
463+
}
464+
}
402465
}
403466

404467
TextView.prototype.recycleNativeView = 'auto';
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,45 @@
1+
import { Property } from '../core/properties';
2+
import { booleanConverter } from '../core/view-base';
13
import { TextView as TextViewDefinition } from '.';
24
import { EditableTextBase } from '../editable-text-base';
35

46
export class TextViewBase extends EditableTextBase implements TextViewDefinition {
57
public static returnPressEvent = 'returnPress';
68

79
public maxLines: number;
10+
public isWritingToolsActive: boolean;
11+
public enableWritingToolsEvents: boolean;
812
}
13+
14+
/**
15+
* (iOS Only) Behavior for Apple Intelligence Writing Tools
16+
* @since 8.9
17+
*/
18+
export enum WritingToolsBehavior {
19+
Complete,
20+
Default,
21+
Limited,
22+
None,
23+
}
24+
export const iosWritingToolsBehaviorProperty = new Property<TextViewBase, WritingToolsBehavior>({
25+
name: 'iosWritingToolsBehavior',
26+
defaultValue: WritingToolsBehavior.Default,
27+
});
28+
iosWritingToolsBehaviorProperty.register(TextViewBase);
29+
30+
/**
31+
* (iOS Only) Allowed input for Apple Intelligence Writing Tools
32+
* @since 8.9
33+
*/
34+
export enum WritingToolsAllowedInput {
35+
Default,
36+
List,
37+
PlainText,
38+
RichText,
39+
Table,
40+
}
41+
export const iosWritingToolsAllowedInputProperty = new Property<TextViewBase, Array<WritingToolsAllowedInput>>({
42+
name: 'iosWritingToolsAllowedInput',
43+
defaultValue: [WritingToolsAllowedInput.Default],
44+
});
45+
iosWritingToolsAllowedInputProperty.register(TextViewBase);

packages/core/utils/ios/index.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,11 +192,10 @@ export function createUIDocumentInteractionControllerDelegate(): NSObject {
192192

193193
export function isRealDevice() {
194194
try {
195-
// https://stackoverflow.com/a/5093092/4936697
196-
const sourceType = UIImagePickerControllerSourceType.UIImagePickerControllerSourceTypeCamera;
197-
const mediaTypes = UIImagePickerController.availableMediaTypesForSourceType(sourceType);
198-
199-
return !!mediaTypes;
195+
if (NSProcessInfo.processInfo.environment.valueForKey('SIMULATOR_DEVICE_NAME')) {
196+
return false;
197+
}
198+
return true;
200199
} catch (e) {
201200
return true;
202201
}

packages/types-ios/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nativescript/types-ios",
3-
"version": "8.8.0",
3+
"version": "8.9.0-alpha.0",
44
"description": "NativeScript Types for iOS.",
55
"homepage": "https://nativescript.org",
66
"repository": {

0 commit comments

Comments
 (0)