| 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 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