| Flutter iOS Embedder |

TextInputSemanticsObject.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "flutter/shell/platform/darwin/ios/framework/Source/TextInputSemanticsObject.h"
6
7 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
8
9 FLUTTER_ASSERT_ARC
10
11 static const UIAccessibilityTraits kUIAccessibilityTraitUndocumentedEmptyLine = 0x800000000000;
12
13 /**
14 * An implementation of `UITextInput` used for text fields that do not currently
15 * have input focus.
16 *
17 * This class is used by `TextInputSemanticsObject`.
18 */
19 @interface FlutterInactiveTextInput : UIView <UITextInput>
20 @property(nonatomic, copy) NSString* text;
21 @end
22
23 @implementation FlutterInactiveTextInput
24
25 // Synthesize properties declared in UITextInput protocol.
26 @synthesize beginningOfDocument = _beginningOfDocument;
27 @synthesize endOfDocument = _endOfDocument;
28 @synthesize inputDelegate = _inputDelegate;
29 @synthesize markedTextRange = _markedTextRange;
30 @synthesize markedTextStyle = _markedTextStyle;
31 @synthesize selectedTextRange = _selectedTextRange;
32 @synthesize tokenizer = _tokenizer;
33
34 - (BOOL)hasText {
35 return self.text.length > 0;
36 }
37
38 - (NSString*)textInRange:(UITextRange*)range {
39 if (!range) {
40 return nil;
41 }
42 NSAssert([range isKindOfClass:[FlutterTextRange class]],
43 @"Expected a FlutterTextRange for range (got %@).", [range class]);
44 NSRange textRange = ((FlutterTextRange*)range).range;
45 if (textRange.location == NSNotFound) {
46 // Avoids [crashes](https://github.com/flutter/flutter/issues/138464) from an assertion
47 // against NSNotFound.
48 // TODO(hellohuanlin): This is a temp workaround, but we should look into why
49 // framework is providing NSNotFound to the engine.
50 // https://github.com/flutter/flutter/issues/160100
51 return nil;
52 }
53 return [self.text substringWithRange:textRange];
54 }
55
56 - (void)replaceRange:(UITextRange*)range withText:(NSString*)text {
57 // This method is required but not called by accessibility API for
58 // features we are using it for. It may need to be implemented if
59 // requirements change.
60 }
61
62 - (void)setMarkedText:(NSString*)markedText selectedRange:(NSRange)markedSelectedRange {
63 // This method is required but not called by accessibility API for
64 // features we are using it for. It may need to be implemented if
65 // requirements change.
66 }
67
68 - (void)unmarkText {
69 // This method is required but not called by accessibility API for
70 // features we are using it for. It may need to be implemented if
71 // requirements change.
72 }
73
74 - (UITextRange*)textRangeFromPosition:(UITextPosition*)fromPosition
75 toPosition:(UITextPosition*)toPosition {
76 NSUInteger fromIndex = ((FlutterTextPosition*)fromPosition).index;
77 NSUInteger toIndex = ((FlutterTextPosition*)toPosition).index;
78 return [FlutterTextRange rangeWithNSRange:NSMakeRange(fromIndex, toIndex - fromIndex)];
79 }
80
81 - (UITextPosition*)positionFromPosition:(UITextPosition*)position offset:(NSInteger)offset {
82 // This method is required but not called by accessibility API for
83 // features we are using it for. It may need to be implemented if
84 // requirements change.
85 return nil;
86 }
87
88 - (UITextPosition*)positionFromPosition:(UITextPosition*)position
89 inDirection:(UITextLayoutDirection)direction
90 offset:(NSInteger)offset {
91 // This method is required but not called by accessibility API for
92 // features we are using it for. It may need to be implemented if
93 // requirements change.
94 return nil;
95 }
96
97 - (NSComparisonResult)comparePosition:(UITextPosition*)position toPosition:(UITextPosition*)other {
98 // This method is required but not called by accessibility API for
99 // features we are using it for. It may need to be implemented if
100 // requirements change.
101 return NSOrderedSame;
102 }
103
104 - (NSInteger)offsetFromPosition:(UITextPosition*)from toPosition:(UITextPosition*)toPosition {
105 // This method is required but not called by accessibility API for
106 // features we are using it for. It may need to be implemented if
107 // requirements change.
108 return 0;
109 }
110
111 - (UITextPosition*)positionWithinRange:(UITextRange*)range
112 farthestInDirection:(UITextLayoutDirection)direction {
113 // This method is required but not called by accessibility API for
114 // features we are using it for. It may need to be implemented if
115 // requirements change.
116 return nil;
117 }
118
119 - (UITextRange*)characterRangeByExtendingPosition:(UITextPosition*)position
120 inDirection:(UITextLayoutDirection)direction {
121 // This method is required but not called by accessibility API for
122 // features we are using it for. It may need to be implemented if
123 // requirements change.
124 return nil;
125 }
126
127 - (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition*)position
128 inDirection:(UITextStorageDirection)direction {
129 // Not editable. Does not apply.
130 return UITextWritingDirectionNatural;
131 }
132
133 - (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection
134 forRange:(UITextRange*)range {
135 // Not editable. Does not apply.
136 }
137
138 - (CGRect)firstRectForRange:(UITextRange*)range {
139 // This method is required but not called by accessibility API for
140 // features we are using it for. It may need to be implemented if
141 // requirements change.
142 return CGRectZero;
143 }
144
145 - (CGRect)caretRectForPosition:(UITextPosition*)position {
146 // This method is required but not called by accessibility API for
147 // features we are using it for. It may need to be implemented if
148 // requirements change.
149 return CGRectZero;
150 }
151
152 - (UITextPosition*)closestPositionToPoint:(CGPoint)point {
153 // This method is required but not called by accessibility API for
154 // features we are using it for. It may need to be implemented if
155 // requirements change.
156 return nil;
157 }
158
159 - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange*)range {
160 // This method is required but not called by accessibility API for
161 // features we are using it for. It may need to be implemented if
162 // requirements change.
163 return nil;
164 }
165
166 - (NSArray*)selectionRectsForRange:(UITextRange*)range {
167 // This method is required but not called by accessibility API for
168 // features we are using it for. It may need to be implemented if
169 // requirements change.
170 return @[];
171 }
172
173 - (UITextRange*)characterRangeAtPoint:(CGPoint)point {
174 // This method is required but not called by accessibility API for
175 // features we are using it for. It may need to be implemented if
176 // requirements change.
177 return nil;
178 }
179
180 - (void)insertText:(NSString*)text {
181 // This method is required but not called by accessibility API for
182 // features we are using it for. It may need to be implemented if
183 // requirements change.
184 }
185
186 - (void)deleteBackward {
187 // This method is required but not called by accessibility API for
188 // features we are using it for. It may need to be implemented if
189 // requirements change.
190 }
191
192 @end
193
194 @implementation TextInputSemanticsObject {
195 FlutterInactiveTextInput* _inactive_text_input;
196 }
197
198 - (instancetype)initWithBridge:(fml::WeakPtr<flutter::AccessibilityBridgeIos>)bridge
199 uid:(int32_t)uid {
200 self = [super initWithBridge:bridge uid:uid];
201
202 if (self) {
203 _inactive_text_input = [[FlutterInactiveTextInput alloc] init];
204 }
205
206 return self;
207 }
208
209 #pragma mark - SemanticsObject overrides
210
211 - (void)setSemanticsNode:(const flutter::SemanticsNode*)node {
212 [super setSemanticsNode:node];
213 _inactive_text_input.text = @(node->value.data());
214 FlutterTextInputView* textInput = (FlutterTextInputView*)[self bridge]->textInputView();
215 if ([self node].flags.isFocused) {
216 textInput.backingTextInputAccessibilityObject = self;
217 // The text input view must have a non-trivial size for the accessibility
218 // system to send text editing events.
219 textInput.frame = CGRectMake(0.0, 0.0, 1.0, 1.0);
220 } else if (textInput.backingTextInputAccessibilityObject == self) {
221 textInput.backingTextInputAccessibilityObject = nil;
222 }
223 }
224
225 #pragma mark - UIAccessibility overrides
226
227 /**
228 * The UITextInput whose accessibility properties we present to UIKit as
229 * substitutes for Flutter's text field properties.
230 *
231 * When the field is currently focused (i.e. it is being edited), we use
232 * the FlutterTextInputView used by FlutterTextInputPlugin. Otherwise,
233 * we use an FlutterInactiveTextInput.
234 */
235 - (UIView<UITextInput>*)textInputSurrogate {
236 if ([self node].flags.isFocused) {
237 return [self bridge]->textInputView();
238 } else {
239 return _inactive_text_input;
240 }
241 }
242
243 - (UIView*)textInputView {
244 return [self textInputSurrogate];
245 }
246
247 - (void)accessibilityElementDidBecomeFocused {
248 if (![self isAccessibilityBridgeAlive]) {
249 return;
250 }
251 [[self textInputSurrogate] accessibilityElementDidBecomeFocused];
252 [super accessibilityElementDidBecomeFocused];
253 }
254
255 - (void)accessibilityElementDidLoseFocus {
256 if (![self isAccessibilityBridgeAlive]) {
257 return;
258 }
259 [[self textInputSurrogate] accessibilityElementDidLoseFocus];
260 [super accessibilityElementDidLoseFocus];
261 }
262
263 - (BOOL)accessibilityElementIsFocused {
264 if (![self isAccessibilityBridgeAlive]) {
265 return false;
266 }
267 return [self node].flags.isFocused;
268 }
269
270 - (BOOL)accessibilityActivate {
271 if (![self isAccessibilityBridgeAlive]) {
272 return false;
273 }
274 return [[self textInputSurrogate] accessibilityActivate];
275 }
276
277 - (NSString*)accessibilityLabel {
278 if (![self isAccessibilityBridgeAlive]) {
279 return nil;
280 }
281
282 NSString* label = [super accessibilityLabel];
283 if (label != nil) {
284 return label;
285 }
286 return [self textInputSurrogate].accessibilityLabel;
287 }
288
289 - (NSString*)accessibilityHint {
290 if (![self isAccessibilityBridgeAlive]) {
291 return nil;
292 }
293 NSString* hint = [super accessibilityHint];
294 if (hint != nil) {
295 return hint;
296 }
297 return [self textInputSurrogate].accessibilityHint;
298 }
299
300 - (NSString*)accessibilityValue {
301 if (![self isAccessibilityBridgeAlive]) {
302 return nil;
303 }
304 NSString* value = [super accessibilityValue];
305 if (value != nil) {
306 return value;
307 }
308 return [self textInputSurrogate].accessibilityValue;
309 }
310
311 - (UIAccessibilityTraits)accessibilityTraits {
312 if (![self isAccessibilityBridgeAlive]) {
313 return 0;
314 }
315 UIAccessibilityTraits results =
316 [super accessibilityTraits] | [self textInputSurrogate].accessibilityTraits;
317 // We remove an undocumented flag to get rid of a bug where single-tapping
318 // a text input field incorrectly says "empty line".
319 // See also: https://github.com/flutter/flutter/issues/52487
320 return results & (~kUIAccessibilityTraitUndocumentedEmptyLine);
321 }
322
323 #pragma mark - UITextInput overrides
324
325 - (NSString*)textInRange:(UITextRange*)range {
326 return [[self textInputSurrogate] textInRange:range];
327 }
328
329 - (void)replaceRange:(UITextRange*)range withText:(NSString*)text {
330 return [[self textInputSurrogate] replaceRange:range withText:text];
331 }
332
333 - (BOOL)shouldChangeTextInRange:(UITextRange*)range replacementText:(NSString*)text {
334 return [[self textInputSurrogate] shouldChangeTextInRange:range replacementText:text];
335 }
336
337 - (UITextRange*)selectedTextRange {
338 return [[self textInputSurrogate] selectedTextRange];
339 }
340
341 - (void)setSelectedTextRange:(UITextRange*)range {
342 [[self textInputSurrogate] setSelectedTextRange:range];
343 }
344
345 - (UITextRange*)markedTextRange {
346 return [[self textInputSurrogate] markedTextRange];
347 }
348
349 - (NSDictionary*)markedTextStyle {
350 return [[self textInputSurrogate] markedTextStyle];
351 }
352
353 - (void)setMarkedTextStyle:(NSDictionary*)style {
354 [[self textInputSurrogate] setMarkedTextStyle:style];
355 }
356
357 - (void)setMarkedText:(NSString*)markedText selectedRange:(NSRange)selectedRange {
358 [[self textInputSurrogate] setMarkedText:markedText selectedRange:selectedRange];
359 }
360
361 - (void)unmarkText {
362 [[self textInputSurrogate] unmarkText];
363 }
364
365 - (UITextStorageDirection)selectionAffinity {
366 return [[self textInputSurrogate] selectionAffinity];
367 }
368
369 - (UITextPosition*)beginningOfDocument {
370 return [[self textInputSurrogate] beginningOfDocument];
371 }
372
373 - (UITextPosition*)endOfDocument {
374 return [[self textInputSurrogate] endOfDocument];
375 }
376
377 - (id<UITextInputDelegate>)inputDelegate {
378 return [[self textInputSurrogate] inputDelegate];
379 }
380
381 - (void)setInputDelegate:(id<UITextInputDelegate>)delegate {
382 [[self textInputSurrogate] setInputDelegate:delegate];
383 }
384
385 - (id<UITextInputTokenizer>)tokenizer {
386 return [[self textInputSurrogate] tokenizer];
387 }
388
389 - (UITextRange*)textRangeFromPosition:(UITextPosition*)fromPosition
390 toPosition:(UITextPosition*)toPosition {
391 return [[self textInputSurrogate] textRangeFromPosition:fromPosition toPosition:toPosition];
392 }
393
394 - (UITextPosition*)positionFromPosition:(UITextPosition*)position offset:(NSInteger)offset {
395 return [[self textInputSurrogate] positionFromPosition:position offset:offset];
396 }
397
398 - (UITextPosition*)positionFromPosition:(UITextPosition*)position
399 inDirection:(UITextLayoutDirection)direction
400 offset:(NSInteger)offset {
401 return [[self textInputSurrogate] positionFromPosition:position
402 inDirection:direction
403 offset:offset];
404 }
405
406 - (NSComparisonResult)comparePosition:(UITextPosition*)position toPosition:(UITextPosition*)other {
407 return [[self textInputSurrogate] comparePosition:position toPosition:other];
408 }
409
410 - (NSInteger)offsetFromPosition:(UITextPosition*)from toPosition:(UITextPosition*)toPosition {
411 return [[self textInputSurrogate] offsetFromPosition:from toPosition:toPosition];
412 }
413
414 - (UITextPosition*)positionWithinRange:(UITextRange*)range
415 farthestInDirection:(UITextLayoutDirection)direction {
416 return [[self textInputSurrogate] positionWithinRange:range farthestInDirection:direction];
417 }
418
419 - (UITextRange*)characterRangeByExtendingPosition:(UITextPosition*)position
420 inDirection:(UITextLayoutDirection)direction {
421 return [[self textInputSurrogate] characterRangeByExtendingPosition:position
422 inDirection:direction];
423 }
424
425 - (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition*)position
426 inDirection:(UITextStorageDirection)direction {
427 return [[self textInputSurrogate] baseWritingDirectionForPosition:position inDirection:direction];
428 }
429
430 - (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection
431 forRange:(UITextRange*)range {
432 [[self textInputSurrogate] setBaseWritingDirection:writingDirection forRange:range];
433 }
434
435 - (CGRect)firstRectForRange:(UITextRange*)range {
436 return [[self textInputSurrogate] firstRectForRange:range];
437 }
438
439 - (CGRect)caretRectForPosition:(UITextPosition*)position {
440 return [[self textInputSurrogate] caretRectForPosition:position];
441 }
442
443 - (UITextPosition*)closestPositionToPoint:(CGPoint)point {
444 return [[self textInputSurrogate] closestPositionToPoint:point];
445 }
446
447 - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange*)range {
448 return [[self textInputSurrogate] closestPositionToPoint:point withinRange:range];
449 }
450
451 - (NSArray*)selectionRectsForRange:(UITextRange*)range {
452 return [[self textInputSurrogate] selectionRectsForRange:range];
453 }
454
455 - (UITextRange*)characterRangeAtPoint:(CGPoint)point {
456 return [[self textInputSurrogate] characterRangeAtPoint:point];
457 }
458
459 - (void)insertText:(NSString*)text {
460 [[self textInputSurrogate] insertText:text];
461 }
462
463 - (void)deleteBackward {
464 [[self textInputSurrogate] deleteBackward];
465 }
466
467 #pragma mark - UIKeyInput overrides
468
469 - (BOOL)hasText {
470 return [[self textInputSurrogate] hasText];
471 }
472
473 #pragma mark - UIResponder overrides
474
475 - (void)cut:(id)sender {
476 [[self textInputSurrogate] cut:sender];
477 }
478
479 - (void)copy:(id)sender {
480 [[self textInputSurrogate] copy:sender];
481 }
482
483 - (void)paste:(id)sender {
484 [[self textInputSurrogate] paste:sender];
485 }
486
487 // TODO(hellohuanlin): should also support `select:`, which is not implemented by the surrogate yet.
488 // See: https://github.com/flutter/flutter/issues/107578.
489 - (void)selectAll:(id)sender {
490 [[self textInputSurrogate] selectAll:sender];
491 }
492
493 - (void)delete:(id)sender {
494 [[self textInputSurrogate] delete:sender];
495 }
496
497 - (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
498 return [[self textInputSurrogate] canPerformAction:action withSender:sender];
499 }
500
501 @end
FlutterTextInputPlugin.h
markedTextStyle
NSDictionary * markedTextStyle
Definition: FlutterTextInputPlugin.h:147
markedTextRange
UITextRange * markedTextRange
Definition: FlutterTextInputPlugin.h:146
inputDelegate
id< UITextInputDelegate > inputDelegate
Definition: FlutterTextInputPlugin.h:148
selectedTextRange
API_AVAILABLE(ios(13.0)) @interface FlutterTextPlaceholder UITextRange * selectedTextRange
Definition: FlutterTextInputPlugin.h:134
caretRectForPosition
CGRect caretRectForPosition
Definition: FlutterTextInputPlugin.h:185
_selectedTextRange
FlutterTextRange * _selectedTextRange
Definition: FlutterTextInputPlugin.mm:815
TextInputSemanticsObject.h
kUIAccessibilityTraitUndocumentedEmptyLine
static FLUTTER_ASSERT_ARC const UIAccessibilityTraits kUIAccessibilityTraitUndocumentedEmptyLine
Definition: TextInputSemanticsObject.mm:11
FlutterTextInputView
Definition: FlutterTextInputPlugin.mm:812
FlutterInactiveTextInput
Definition: TextInputSemanticsObject.mm:19
FlutterInactiveTextInput::text
NSString * text
Definition: TextInputSemanticsObject.mm:20
FlutterTextPosition
Definition: FlutterTextInputPlugin.h:77
FlutterTextRange
Definition: FlutterTextInputPlugin.h:88
+[FlutterTextRange rangeWithNSRange:]
instancetype rangeWithNSRange:(NSRange range)
Definition: FlutterTextInputPlugin.mm:550
TextInputSemanticsObject
Definition: TextInputSemanticsObject.h:20
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13