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