Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
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
10static 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 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 const UIAccessibilityTraits kUIAccessibilityTraitUndocumentedEmptyLine
uint8_t value
instancetype rangeWithNSRange:(NSRange range)
NSDictionary * markedTextStyle
UITextRange * markedTextRange
id< UITextInputDelegate > inputDelegate
API_AVAILABLE(ios(13.0)) @interface FlutterTextPlaceholder UITextRange * selectedTextRange
CGRect caretRectForPosition
auto WeakPtr(std::shared_ptr< T > pointer)
const uintptr_t id
int BOOL