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