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