Flutter Engine
The Flutter Engine
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
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 beginningOfDocument = _beginningOfDocument;
26@synthesize endOfDocument = _endOfDocument;
27@synthesize inputDelegate = _inputDelegate;
28@synthesize markedTextRange = _markedTextRange;
29@synthesize markedTextStyle = _markedTextStyle;
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];
245}
246
247- (void)accessibilityElementDidLoseFocus {
248 if (![self isAccessibilityBridgeAlive]) {
249 return;
250 }
251 [[self textInputSurrogate] 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@end
static void copy(void *dst, const uint8_t *src, int width, int bpp, int deltaSrc, int offset, const SkPMColor ctable[])
Definition: SkSwizzler.cpp:31
static FLUTTER_ASSERT_ARC const UIAccessibilityTraits kUIAccessibilityTraitUndocumentedEmptyLine
uint8_t value
instancetype rangeWithNSRange:(NSRange range)
NSString * accessibilityHint()
void accessibilityElementDidBecomeFocused()
NSString * accessibilityValue()
NSString * accessibilityLabel()
flutter::SemanticsNode node
instancetype initWithBridge:uid:(fml::WeakPtr< flutter::AccessibilityBridgeIos > bridge,[uid] int32_t NS_DESIGNATED_INITIALIZER)
void accessibilityElementDidLoseFocus()
fml::WeakPtr< flutter::AccessibilityBridgeIos > bridge
void setSemanticsNode:(const flutter::SemanticsNode *NS_REQUIRES_SUPER)
UIView< UITextInput > * textInputSurrogate()
NSDictionary * markedTextStyle
UITextRange * markedTextRange
id< UITextInputDelegate > inputDelegate
API_AVAILABLE(ios(13.0)) @interface FlutterTextPlaceholder UITextRange * selectedTextRange
CGRect caretRectForPosition
FlutterTextRange * _selectedTextRange
SeparatedVector2 offset
int BOOL
Definition: windows_types.h:37