| Flutter iOS Embedder |

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 kUIAccessibilityTraitUndocumentedEmptyLine = 0x800000000000;
11
12 @implementation FlutterInactiveTextInput {
13 }
14
15 @synthesize tokenizer = _tokenizer;
16 @synthesize beginningOfDocument = _beginningOfDocument;
17 @synthesize endOfDocument = _endOfDocument;
18
19 - (void)dealloc {
20 [_text release];
21 [_markedText release];
22 [_markedTextRange release];
23 [_selectedTextRange release];
24 [_markedTextStyle release];
25 [super dealloc];
26 }
27
28 - (BOOL)hasText {
29 return self.text.length > 0;
30 }
31
32 - (NSString*)textInRange:(UITextRange*)range {
33 if (!range) {
34 return nil;
35 }
36 NSAssert([range isKindOfClass:[FlutterTextRange class]],
37 @"Expected a FlutterTextRange for range (got %@).", [range class]);
38 NSRange textRange = ((FlutterTextRange*)range).range;
39 NSAssert(textRange.location != NSNotFound, @"Expected a valid text range.");
40 return [self.text substringWithRange:textRange];
41 }
42
43 - (void)replaceRange:(UITextRange*)range withText:(NSString*)text {
44 // This method is required but not called by accessibility API for
45 // features we are using it for. It may need to be implemented if
46 // requirements change.
47 }
48
49 - (void)setMarkedText:(NSString*)markedText selectedRange:(NSRange)markedSelectedRange {
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)unmarkText {
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 - (UITextRange*)textRangeFromPosition:(UITextPosition*)fromPosition
62 toPosition:(UITextPosition*)toPosition {
63 NSUInteger fromIndex = ((FlutterTextPosition*)fromPosition).index;
64 NSUInteger toIndex = ((FlutterTextPosition*)toPosition).index;
65 return [FlutterTextRange rangeWithNSRange:NSMakeRange(fromIndex, toIndex - fromIndex)];
66 }
67
68 - (UITextPosition*)positionFromPosition:(UITextPosition*)position offset:(NSInteger)offset {
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 return nil;
73 }
74
75 - (UITextPosition*)positionFromPosition:(UITextPosition*)position
76 inDirection:(UITextLayoutDirection)direction
77 offset:(NSInteger)offset {
78 // This method is required but not called by accessibility API for
79 // features we are using it for. It may need to be implemented if
80 // requirements change.
81 return nil;
82 }
83
84 - (NSComparisonResult)comparePosition:(UITextPosition*)position toPosition:(UITextPosition*)other {
85 // This method is required but not called by accessibility API for
86 // features we are using it for. It may need to be implemented if
87 // requirements change.
88 return NSOrderedSame;
89 }
90
91 - (NSInteger)offsetFromPosition:(UITextPosition*)from toPosition:(UITextPosition*)toPosition {
92 // This method is required but not called by accessibility API for
93 // features we are using it for. It may need to be implemented if
94 // requirements change.
95 return 0;
96 }
97
98 - (UITextPosition*)positionWithinRange:(UITextRange*)range
99 farthestInDirection:(UITextLayoutDirection)direction {
100 // This method is required but not called by accessibility API for
101 // features we are using it for. It may need to be implemented if
102 // requirements change.
103 return nil;
104 }
105
106 - (UITextRange*)characterRangeByExtendingPosition:(UITextPosition*)position
107 inDirection:(UITextLayoutDirection)direction {
108 // This method is required but not called by accessibility API for
109 // features we are using it for. It may need to be implemented if
110 // requirements change.
111 return nil;
112 }
113
114 - (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition*)position
115 inDirection:(UITextStorageDirection)direction {
116 // Not editable. Does not apply.
117 return UITextWritingDirectionNatural;
118 }
119
120 - (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection
121 forRange:(UITextRange*)range {
122 // Not editable. Does not apply.
123 }
124
125 - (CGRect)firstRectForRange:(UITextRange*)range {
126 // This method is required but not called by accessibility API for
127 // features we are using it for. It may need to be implemented if
128 // requirements change.
129 return CGRectZero;
130 }
131
132 - (CGRect)caretRectForPosition:(UITextPosition*)position {
133 // This method is required but not called by accessibility API for
134 // features we are using it for. It may need to be implemented if
135 // requirements change.
136 return CGRectZero;
137 }
138
139 - (UITextPosition*)closestPositionToPoint:(CGPoint)point {
140 // This method is required but not called by accessibility API for
141 // features we are using it for. It may need to be implemented if
142 // requirements change.
143 return nil;
144 }
145
146 - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange*)range {
147 // This method is required but not called by accessibility API for
148 // features we are using it for. It may need to be implemented if
149 // requirements change.
150 return nil;
151 }
152
153 - (NSArray*)selectionRectsForRange:(UITextRange*)range {
154 // This method is required but not called by accessibility API for
155 // features we are using it for. It may need to be implemented if
156 // requirements change.
157 return @[];
158 }
159
160 - (UITextRange*)characterRangeAtPoint:(CGPoint)point {
161 // This method is required but not called by accessibility API for
162 // features we are using it for. It may need to be implemented if
163 // requirements change.
164 return nil;
165 }
166
167 - (void)insertText:(NSString*)text {
168 // This method is required but not called by accessibility API for
169 // features we are using it for. It may need to be implemented if
170 // requirements change.
171 }
172
173 - (void)deleteBackward {
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 @end
180
181 @implementation TextInputSemanticsObject {
182 FlutterInactiveTextInput* _inactive_text_input;
183 }
184
185 - (instancetype)initWithBridge:(fml::WeakPtr<flutter::AccessibilityBridgeIos>)bridge
186 uid:(int32_t)uid {
187 self = [super initWithBridge:bridge uid:uid];
188
189 if (self) {
190 _inactive_text_input = [[FlutterInactiveTextInput alloc] init];
191 }
192
193 return self;
194 }
195
196 - (void)dealloc {
197 [_inactive_text_input release];
198 [super dealloc];
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 // Adding UIAccessibilityTraitKeyboardKey to the trait list so that iOS treats it like
308 // a keyboard entry control, thus adding support for text editing features, such as
309 // pinch to select text, and up/down fling to move cursor.
310 UIAccessibilityTraits results = [super accessibilityTraits] |
311 [self textInputSurrogate].accessibilityTraits |
312 UIAccessibilityTraitKeyboardKey;
313 // We remove an undocumented flag to get rid of a bug where single-tapping
314 // a text input field incorrectly says "empty line".
315 // See also: https://github.com/flutter/flutter/issues/52487
316 return results & (~kUIAccessibilityTraitUndocumentedEmptyLine);
317 }
318
319 #pragma mark - UITextInput overrides
320
321 - (NSString*)textInRange:(UITextRange*)range {
322 return [[self textInputSurrogate] textInRange:range];
323 }
324
325 - (void)replaceRange:(UITextRange*)range withText:(NSString*)text {
326 return [[self textInputSurrogate] replaceRange:range withText:text];
327 }
328
329 - (BOOL)shouldChangeTextInRange:(UITextRange*)range replacementText:(NSString*)text {
330 return [[self textInputSurrogate] shouldChangeTextInRange:range replacementText:text];
331 }
332
333 - (UITextRange*)selectedTextRange {
334 return [[self textInputSurrogate] selectedTextRange];
335 }
336
337 - (void)setSelectedTextRange:(UITextRange*)range {
338 [[self textInputSurrogate] setSelectedTextRange:range];
339 }
340
341 - (UITextRange*)markedTextRange {
342 return [[self textInputSurrogate] markedTextRange];
343 }
344
345 - (NSDictionary*)markedTextStyle {
346 return [[self textInputSurrogate] markedTextStyle];
347 }
348
349 - (void)setMarkedTextStyle:(NSDictionary*)style {
350 [[self textInputSurrogate] setMarkedTextStyle:style];
351 }
352
353 - (void)setMarkedText:(NSString*)markedText selectedRange:(NSRange)selectedRange {
354 [[self textInputSurrogate] setMarkedText:markedText selectedRange:selectedRange];
355 }
356
357 - (void)unmarkText {
358 [[self textInputSurrogate] unmarkText];
359 }
360
361 - (UITextStorageDirection)selectionAffinity {
362 return [[self textInputSurrogate] selectionAffinity];
363 }
364
365 - (UITextPosition*)beginningOfDocument {
366 return [[self textInputSurrogate] beginningOfDocument];
367 }
368
369 - (UITextPosition*)endOfDocument {
370 return [[self textInputSurrogate] endOfDocument];
371 }
372
373 - (id<UITextInputDelegate>)inputDelegate {
374 return [[self textInputSurrogate] inputDelegate];
375 }
376
377 - (void)setInputDelegate:(id<UITextInputDelegate>)delegate {
378 [[self textInputSurrogate] setInputDelegate:delegate];
379 }
380
381 - (id<UITextInputTokenizer>)tokenizer {
382 return [[self textInputSurrogate] tokenizer];
383 }
384
385 - (UITextRange*)textRangeFromPosition:(UITextPosition*)fromPosition
386 toPosition:(UITextPosition*)toPosition {
387 return [[self textInputSurrogate] textRangeFromPosition:fromPosition toPosition:toPosition];
388 }
389
390 - (UITextPosition*)positionFromPosition:(UITextPosition*)position offset:(NSInteger)offset {
391 return [[self textInputSurrogate] positionFromPosition:position offset:offset];
392 }
393
394 - (UITextPosition*)positionFromPosition:(UITextPosition*)position
395 inDirection:(UITextLayoutDirection)direction
396 offset:(NSInteger)offset {
397 return [[self textInputSurrogate] positionFromPosition:position
398 inDirection:direction
399 offset:offset];
400 }
401
402 - (NSComparisonResult)comparePosition:(UITextPosition*)position toPosition:(UITextPosition*)other {
403 return [[self textInputSurrogate] comparePosition:position toPosition:other];
404 }
405
406 - (NSInteger)offsetFromPosition:(UITextPosition*)from toPosition:(UITextPosition*)toPosition {
407 return [[self textInputSurrogate] offsetFromPosition:from toPosition:toPosition];
408 }
409
410 - (UITextPosition*)positionWithinRange:(UITextRange*)range
411 farthestInDirection:(UITextLayoutDirection)direction {
412 return [[self textInputSurrogate] positionWithinRange:range farthestInDirection:direction];
413 }
414
415 - (UITextRange*)characterRangeByExtendingPosition:(UITextPosition*)position
416 inDirection:(UITextLayoutDirection)direction {
417 return [[self textInputSurrogate] characterRangeByExtendingPosition:position
418 inDirection:direction];
419 }
420
421 - (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition*)position
422 inDirection:(UITextStorageDirection)direction {
423 return [[self textInputSurrogate] baseWritingDirectionForPosition:position inDirection:direction];
424 }
425
426 - (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection
427 forRange:(UITextRange*)range {
428 [[self textInputSurrogate] setBaseWritingDirection:writingDirection forRange:range];
429 }
430
431 - (CGRect)firstRectForRange:(UITextRange*)range {
432 return [[self textInputSurrogate] firstRectForRange:range];
433 }
434
435 - (CGRect)caretRectForPosition:(UITextPosition*)position {
436 return [[self textInputSurrogate] caretRectForPosition:position];
437 }
438
439 - (UITextPosition*)closestPositionToPoint:(CGPoint)point {
440 return [[self textInputSurrogate] closestPositionToPoint:point];
441 }
442
443 - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange*)range {
444 return [[self textInputSurrogate] closestPositionToPoint:point withinRange:range];
445 }
446
447 - (NSArray*)selectionRectsForRange:(UITextRange*)range {
448 return [[self textInputSurrogate] selectionRectsForRange:range];
449 }
450
451 - (UITextRange*)characterRangeAtPoint:(CGPoint)point {
452 return [[self textInputSurrogate] characterRangeAtPoint:point];
453 }
454
455 - (void)insertText:(NSString*)text {
456 [[self textInputSurrogate] insertText:text];
457 }
458
459 - (void)deleteBackward {
460 [[self textInputSurrogate] deleteBackward];
461 }
462
463 #pragma mark - UIKeyInput overrides
464
465 - (BOOL)hasText {
466 return [[self textInputSurrogate] hasText];
467 }
468
469 @end
caretRectForPosition
CGRect caretRectForPosition
Definition: FlutterTextInputPlugin.h:173
kUIAccessibilityTraitUndocumentedEmptyLine
static const UIAccessibilityTraits kUIAccessibilityTraitUndocumentedEmptyLine
Definition: accessibility_text_entry.mm:10
FlutterTextRange
Definition: FlutterTextInputPlugin.h:79
FlutterInactiveTextInput
Definition: accessibility_text_entry.h:14
+[FlutterTextRange rangeWithNSRange:]
instancetype rangeWithNSRange:(NSRange range)
Definition: FlutterTextInputPlugin.mm:542
FlutterTextInputView
Definition: FlutterTextInputPlugin.mm:801
selectedTextRange
API_AVAILABLE(ios(13.0)) @interface FlutterTextPlaceholder UITextRange * selectedTextRange
Definition: FlutterTextInputPlugin.h:125
flutter
Definition: accessibility_bridge.h:28
accessibility_bridge.h
accessibility_text_entry.h
inputDelegate
id< UITextInputDelegate > inputDelegate
Definition: FlutterTextInputPlugin.h:138
FlutterInactiveTextInput::text
NSString * text
Definition: accessibility_text_entry.h:16
TextInputSemanticsObject
Definition: accessibility_text_entry.h:33
FlutterTextPosition
Definition: FlutterTextInputPlugin.h:67
markedTextStyle
NSDictionary * markedTextStyle
Definition: FlutterTextInputPlugin.h:137
markedTextRange
UITextRange * markedTextRange
Definition: FlutterTextInputPlugin.h:136