blob: f39f19174d5622cbd8fdaf02efba8449bce243f5 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <UIKit/UIKit.h>
#include "flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h"
#include "flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.h"
static const UIAccessibilityTraits UIAccessibilityTraitUndocumentedEmptyLine = 0x800000000000;
@implementation FlutterInactiveTextInput {
}
@synthesize tokenizer = _tokenizer;
@synthesize beginningOfDocument = _beginningOfDocument;
@synthesize endOfDocument = _endOfDocument;
- (instancetype)init {
return [super init];
}
- (BOOL)hasText {
return self.text.length > 0;
}
- (NSString*)textInRange:(UITextRange*)range {
if (!range) {
return nil;
}
NSAssert([range isKindOfClass:[FlutterTextRange class]],
@"Expected a FlutterTextRange for range (got %@).", [range class]);
NSRange textRange = ((FlutterTextRange*)range).range;
NSAssert(textRange.location != NSNotFound, @"Expected a valid text range.");
return [self.text substringWithRange:textRange];
}
- (void)replaceRange:(UITextRange*)range withText:(NSString*)text {
// This method is required but not called by accessibility API for
// features we are using it for. It may need to be implemented if
// requirements change.
}
- (void)setMarkedText:(NSString*)markedText selectedRange:(NSRange)markedSelectedRange {
// This method is required but not called by accessibility API for
// features we are using it for. It may need to be implemented if
// requirements change.
}
- (void)unmarkText {
// This method is required but not called by accessibility API for
// features we are using it for. It may need to be implemented if
// requirements change.
}
- (UITextRange*)textRangeFromPosition:(UITextPosition*)fromPosition
toPosition:(UITextPosition*)toPosition {
NSUInteger fromIndex = ((FlutterTextPosition*)fromPosition).index;
NSUInteger toIndex = ((FlutterTextPosition*)toPosition).index;
return [FlutterTextRange rangeWithNSRange:NSMakeRange(fromIndex, toIndex - fromIndex)];
}
- (UITextPosition*)positionFromPosition:(UITextPosition*)position offset:(NSInteger)offset {
// This method is required but not called by accessibility API for
// features we are using it for. It may need to be implemented if
// requirements change.
return nil;
}
- (UITextPosition*)positionFromPosition:(UITextPosition*)position
inDirection:(UITextLayoutDirection)direction
offset:(NSInteger)offset {
// This method is required but not called by accessibility API for
// features we are using it for. It may need to be implemented if
// requirements change.
return nil;
}
- (NSComparisonResult)comparePosition:(UITextPosition*)position toPosition:(UITextPosition*)other {
// This method is required but not called by accessibility API for
// features we are using it for. It may need to be implemented if
// requirements change.
return NSOrderedSame;
}
- (NSInteger)offsetFromPosition:(UITextPosition*)from toPosition:(UITextPosition*)toPosition {
// This method is required but not called by accessibility API for
// features we are using it for. It may need to be implemented if
// requirements change.
return 0;
}
- (UITextPosition*)positionWithinRange:(UITextRange*)range
farthestInDirection:(UITextLayoutDirection)direction {
// This method is required but not called by accessibility API for
// features we are using it for. It may need to be implemented if
// requirements change.
return nil;
}
- (UITextRange*)characterRangeByExtendingPosition:(UITextPosition*)position
inDirection:(UITextLayoutDirection)direction {
// This method is required but not called by accessibility API for
// features we are using it for. It may need to be implemented if
// requirements change.
return nil;
}
- (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition*)position
inDirection:(UITextStorageDirection)direction {
// Not editable. Does not apply.
return UITextWritingDirectionNatural;
}
- (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection
forRange:(UITextRange*)range {
// Not editable. Does not apply.
}
- (CGRect)firstRectForRange:(UITextRange*)range {
// This method is required but not called by accessibility API for
// features we are using it for. It may need to be implemented if
// requirements change.
return CGRectZero;
}
- (CGRect)caretRectForPosition:(UITextPosition*)position {
// This method is required but not called by accessibility API for
// features we are using it for. It may need to be implemented if
// requirements change.
return CGRectZero;
}
- (UITextPosition*)closestPositionToPoint:(CGPoint)point {
// This method is required but not called by accessibility API for
// features we are using it for. It may need to be implemented if
// requirements change.
return nil;
}
- (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange*)range {
// This method is required but not called by accessibility API for
// features we are using it for. It may need to be implemented if
// requirements change.
return nil;
}
- (NSArray*)selectionRectsForRange:(UITextRange*)range {
// This method is required but not called by accessibility API for
// features we are using it for. It may need to be implemented if
// requirements change.
return @[];
}
- (UITextRange*)characterRangeAtPoint:(CGPoint)point {
// This method is required but not called by accessibility API for
// features we are using it for. It may need to be implemented if
// requirements change.
return nil;
}
- (void)insertText:(NSString*)text {
// This method is required but not called by accessibility API for
// features we are using it for. It may need to be implemented if
// requirements change.
}
- (void)deleteBackward {
// This method is required but not called by accessibility API for
// features we are using it for. It may need to be implemented if
// requirements change.
}
@end
@implementation TextInputSemanticsObject {
FlutterInactiveTextInput* _inactive_text_input;
}
- (instancetype)initWithBridge:(fml::WeakPtr<flutter::AccessibilityBridgeIos>)bridge
uid:(int32_t)uid {
self = [super initWithBridge:bridge uid:uid];
if (self) {
_inactive_text_input = [[FlutterInactiveTextInput alloc] init];
}
return self;
}
- (void)dealloc {
[_inactive_text_input release];
[super dealloc];
}
#pragma mark - SemanticsObject overrides
- (void)setSemanticsNode:(const flutter::SemanticsNode*)node {
[super setSemanticsNode:node];
_inactive_text_input.text = @(node->value.data());
if ([self node].HasFlag(flutter::SemanticsFlags::kIsFocused)) {
// The text input view must have a non-trivial size for the accessibility
// system to send text editing events.
[self bridge]->textInputView().frame = CGRectMake(0.0, 0.0, 1.0, 1.0);
}
}
#pragma mark - UIAccessibility overrides
/**
* The UITextInput whose accessibility properties we present to UIKit as
* substitutes for Flutter's text field properties.
*
* When the field is currently focused (i.e. it is being edited), we use
* the FlutterTextInputView used by FlutterTextInputPlugin. Otherwise,
* we use an FlutterInactiveTextInput.
*/
- (UIView<UITextInput>*)textInputSurrogate {
if ([self node].HasFlag(flutter::SemanticsFlags::kIsFocused)) {
return [self bridge]->textInputView();
} else {
return _inactive_text_input;
}
}
- (UIView*)textInputView {
return [self textInputSurrogate];
}
- (void)accessibilityElementDidBecomeFocused {
if (![self isAccessibilityBridgeAlive])
return;
[[self textInputSurrogate] accessibilityElementDidBecomeFocused];
[super accessibilityElementDidBecomeFocused];
}
- (void)accessibilityElementDidLoseFocus {
if (![self isAccessibilityBridgeAlive])
return;
[[self textInputSurrogate] accessibilityElementDidLoseFocus];
[super accessibilityElementDidLoseFocus];
}
- (BOOL)accessibilityElementIsFocused {
if (![self isAccessibilityBridgeAlive])
return false;
return [self node].HasFlag(flutter::SemanticsFlags::kIsFocused);
}
- (BOOL)accessibilityActivate {
if (![self isAccessibilityBridgeAlive])
return false;
return [[self textInputSurrogate] accessibilityActivate];
}
- (NSString*)accessibilityLabel {
if (![self isAccessibilityBridgeAlive])
return nil;
NSString* label = [super accessibilityLabel];
if (label != nil)
return label;
return [self textInputSurrogate].accessibilityLabel;
}
- (NSString*)accessibilityHint {
if (![self isAccessibilityBridgeAlive])
return nil;
NSString* hint = [super accessibilityHint];
if (hint != nil)
return hint;
return [self textInputSurrogate].accessibilityHint;
}
- (NSString*)accessibilityValue {
if (![self isAccessibilityBridgeAlive])
return nil;
NSString* value = [super accessibilityValue];
if (value != nil)
return value;
return [self textInputSurrogate].accessibilityValue;
}
- (UIAccessibilityTraits)accessibilityTraits {
if (![self isAccessibilityBridgeAlive])
return 0;
// Adding UIAccessibilityTraitKeyboardKey to the trait list so that iOS treats it like
// a keyboard entry control, thus adding support for text editing features, such as
// pinch to select text, and up/down fling to move cursor.
UIAccessibilityTraits results = [super accessibilityTraits] |
[self textInputSurrogate].accessibilityTraits |
UIAccessibilityTraitKeyboardKey;
// We remove an undocumented flag to get rid of a bug where single-tapping
// a text input field incorrectly says "empty line".
// See also: https://github.com/flutter/flutter/issues/52487
return results & (~UIAccessibilityTraitUndocumentedEmptyLine);
}
#pragma mark - UITextInput overrides
- (NSString*)textInRange:(UITextRange*)range {
return [[self textInputSurrogate] textInRange:range];
}
- (void)replaceRange:(UITextRange*)range withText:(NSString*)text {
return [[self textInputSurrogate] replaceRange:range withText:text];
}
- (BOOL)shouldChangeTextInRange:(UITextRange*)range replacementText:(NSString*)text {
return [[self textInputSurrogate] shouldChangeTextInRange:range replacementText:text];
}
- (UITextRange*)selectedTextRange {
return [[self textInputSurrogate] selectedTextRange];
}
- (void)setSelectedTextRange:(UITextRange*)range {
[[self textInputSurrogate] setSelectedTextRange:range];
}
- (UITextRange*)markedTextRange {
return [[self textInputSurrogate] markedTextRange];
}
- (NSDictionary*)markedTextStyle {
return [[self textInputSurrogate] markedTextStyle];
}
- (void)setMarkedTextStyle:(NSDictionary*)style {
[[self textInputSurrogate] setMarkedTextStyle:style];
}
- (void)setMarkedText:(NSString*)markedText selectedRange:(NSRange)selectedRange {
[[self textInputSurrogate] setMarkedText:markedText selectedRange:selectedRange];
}
- (void)unmarkText {
[[self textInputSurrogate] unmarkText];
}
- (UITextStorageDirection)selectionAffinity {
return [[self textInputSurrogate] selectionAffinity];
}
- (UITextPosition*)beginningOfDocument {
return [[self textInputSurrogate] beginningOfDocument];
}
- (UITextPosition*)endOfDocument {
return [[self textInputSurrogate] endOfDocument];
}
- (id<UITextInputDelegate>)inputDelegate {
return [[self textInputSurrogate] inputDelegate];
}
- (void)setInputDelegate:(id<UITextInputDelegate>)delegate {
[[self textInputSurrogate] setInputDelegate:delegate];
}
- (id<UITextInputTokenizer>)tokenizer {
return [[self textInputSurrogate] tokenizer];
}
- (UITextRange*)textRangeFromPosition:(UITextPosition*)fromPosition
toPosition:(UITextPosition*)toPosition {
return [[self textInputSurrogate] textRangeFromPosition:fromPosition toPosition:toPosition];
}
- (UITextPosition*)positionFromPosition:(UITextPosition*)position offset:(NSInteger)offset {
return [[self textInputSurrogate] positionFromPosition:position offset:offset];
}
- (UITextPosition*)positionFromPosition:(UITextPosition*)position
inDirection:(UITextLayoutDirection)direction
offset:(NSInteger)offset {
return [[self textInputSurrogate] positionFromPosition:position
inDirection:direction
offset:offset];
}
- (NSComparisonResult)comparePosition:(UITextPosition*)position toPosition:(UITextPosition*)other {
return [[self textInputSurrogate] comparePosition:position toPosition:other];
}
- (NSInteger)offsetFromPosition:(UITextPosition*)from toPosition:(UITextPosition*)toPosition {
return [[self textInputSurrogate] offsetFromPosition:from toPosition:toPosition];
}
- (UITextPosition*)positionWithinRange:(UITextRange*)range
farthestInDirection:(UITextLayoutDirection)direction {
return [[self textInputSurrogate] positionWithinRange:range farthestInDirection:direction];
}
- (UITextRange*)characterRangeByExtendingPosition:(UITextPosition*)position
inDirection:(UITextLayoutDirection)direction {
return [[self textInputSurrogate] characterRangeByExtendingPosition:position
inDirection:direction];
}
- (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition*)position
inDirection:(UITextStorageDirection)direction {
return [[self textInputSurrogate] baseWritingDirectionForPosition:position inDirection:direction];
}
- (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection
forRange:(UITextRange*)range {
[[self textInputSurrogate] setBaseWritingDirection:writingDirection forRange:range];
}
- (CGRect)firstRectForRange:(UITextRange*)range {
return [[self textInputSurrogate] firstRectForRange:range];
}
- (CGRect)caretRectForPosition:(UITextPosition*)position {
return [[self textInputSurrogate] caretRectForPosition:position];
}
- (UITextPosition*)closestPositionToPoint:(CGPoint)point {
return [[self textInputSurrogate] closestPositionToPoint:point];
}
- (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange*)range {
return [[self textInputSurrogate] closestPositionToPoint:point withinRange:range];
}
- (NSArray*)selectionRectsForRange:(UITextRange*)range {
return [[self textInputSurrogate] selectionRectsForRange:range];
}
- (UITextRange*)characterRangeAtPoint:(CGPoint)point {
return [[self textInputSurrogate] characterRangeAtPoint:point];
}
- (void)insertText:(NSString*)text {
[[self textInputSurrogate] insertText:text];
}
- (void)deleteBackward {
[[self textInputSurrogate] deleteBackward];
}
#pragma mark - UIKeyInput overrides
- (BOOL)hasText {
return [[self textInputSurrogate] hasText];
}
@end