| 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 beginningOfDocument = _beginningOfDocument;
26 @synthesize endOfDocument = _endOfDocument;
27 @synthesize inputDelegate = _inputDelegate;
28 @synthesize markedTextRange = _markedTextRange;
29 @synthesize markedTextStyle = _markedTextStyle;
30 @synthesize selectedTextRange = _selectedTextRange;
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