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