39#pragma mark - TextInput channel method names.
48 @"TextInput.setEditableSizeAndTransform";
59 @"TextInput.onPointerMoveForInteractiveKeyboard";
61 @"TextInput.onPointerUpForInteractiveKeyboard";
63#pragma mark - TextInputConfiguration Field Names
84#pragma mark - Static Functions
89 BOOL gotCodePoint = [
text getBytes:&codePoint
90 maxLength:
sizeof(codePoint)
92 encoding:NSUTF32StringEncoding
96 return gotCodePoint && u_hasBinaryProperty(codePoint, UCHAR_EMOJI);
104 NSString* inputType =
type[
@"name"];
105 return ![inputType isEqualToString:
@"TextInputType.none"];
108 NSString* inputType =
type[
@"name"];
109 if ([inputType isEqualToString:
@"TextInputType.address"]) {
110 return UIKeyboardTypeDefault;
112 if ([inputType isEqualToString:
@"TextInputType.datetime"]) {
113 return UIKeyboardTypeNumbersAndPunctuation;
115 if ([inputType isEqualToString:
@"TextInputType.emailAddress"]) {
116 return UIKeyboardTypeEmailAddress;
118 if ([inputType isEqualToString:
@"TextInputType.multiline"]) {
119 return UIKeyboardTypeDefault;
121 if ([inputType isEqualToString:
@"TextInputType.name"]) {
122 return UIKeyboardTypeNamePhonePad;
124 if ([inputType isEqualToString:
@"TextInputType.number"]) {
125 if ([
type[
@"signed"] boolValue]) {
126 return UIKeyboardTypeNumbersAndPunctuation;
128 if ([
type[
@"decimal"] boolValue]) {
129 return UIKeyboardTypeDecimalPad;
131 return UIKeyboardTypeNumberPad;
133 if ([inputType isEqualToString:
@"TextInputType.phone"]) {
134 return UIKeyboardTypePhonePad;
136 if ([inputType isEqualToString:
@"TextInputType.text"]) {
137 return UIKeyboardTypeDefault;
139 if ([inputType isEqualToString:
@"TextInputType.url"]) {
140 return UIKeyboardTypeURL;
142 if ([inputType isEqualToString:
@"TextInputType.visiblePassword"]) {
143 return UIKeyboardTypeASCIICapable;
145 if ([inputType isEqualToString:
@"TextInputType.webSearch"]) {
146 return UIKeyboardTypeWebSearch;
148 if ([inputType isEqualToString:
@"TextInputType.twitter"]) {
149 return UIKeyboardTypeTwitter;
151 return UIKeyboardTypeDefault;
155 NSString* textCapitalization =
type[
@"textCapitalization"];
156 if ([textCapitalization isEqualToString:
@"TextCapitalization.characters"]) {
157 return UITextAutocapitalizationTypeAllCharacters;
158 }
else if ([textCapitalization isEqualToString:
@"TextCapitalization.sentences"]) {
159 return UITextAutocapitalizationTypeSentences;
160 }
else if ([textCapitalization isEqualToString:
@"TextCapitalization.words"]) {
161 return UITextAutocapitalizationTypeWords;
163 return UITextAutocapitalizationTypeNone;
171 if ([inputType isEqualToString:
@"TextInputAction.unspecified"]) {
172 return UIReturnKeyDefault;
175 if ([inputType isEqualToString:
@"TextInputAction.done"]) {
176 return UIReturnKeyDone;
179 if ([inputType isEqualToString:
@"TextInputAction.go"]) {
180 return UIReturnKeyGo;
183 if ([inputType isEqualToString:
@"TextInputAction.send"]) {
184 return UIReturnKeySend;
187 if ([inputType isEqualToString:
@"TextInputAction.search"]) {
188 return UIReturnKeySearch;
191 if ([inputType isEqualToString:
@"TextInputAction.next"]) {
192 return UIReturnKeyNext;
195 if ([inputType isEqualToString:
@"TextInputAction.continueAction"]) {
196 return UIReturnKeyContinue;
199 if ([inputType isEqualToString:
@"TextInputAction.join"]) {
200 return UIReturnKeyJoin;
203 if ([inputType isEqualToString:
@"TextInputAction.route"]) {
204 return UIReturnKeyRoute;
207 if ([inputType isEqualToString:
@"TextInputAction.emergencyCall"]) {
208 return UIReturnKeyEmergencyCall;
211 if ([inputType isEqualToString:
@"TextInputAction.newline"]) {
212 return UIReturnKeyDefault;
216 return UIReturnKeyDefault;
220 if (!hints || hints.count == 0) {
225 NSString* hint = hints[0];
226 if ([hint isEqualToString:
@"addressCityAndState"]) {
227 return UITextContentTypeAddressCityAndState;
230 if ([hint isEqualToString:
@"addressState"]) {
231 return UITextContentTypeAddressState;
234 if ([hint isEqualToString:
@"addressCity"]) {
235 return UITextContentTypeAddressCity;
238 if ([hint isEqualToString:
@"sublocality"]) {
239 return UITextContentTypeSublocality;
242 if ([hint isEqualToString:
@"streetAddressLine1"]) {
243 return UITextContentTypeStreetAddressLine1;
246 if ([hint isEqualToString:
@"streetAddressLine2"]) {
247 return UITextContentTypeStreetAddressLine2;
250 if ([hint isEqualToString:
@"countryName"]) {
251 return UITextContentTypeCountryName;
254 if ([hint isEqualToString:
@"fullStreetAddress"]) {
255 return UITextContentTypeFullStreetAddress;
258 if ([hint isEqualToString:
@"postalCode"]) {
259 return UITextContentTypePostalCode;
262 if ([hint isEqualToString:
@"location"]) {
263 return UITextContentTypeLocation;
266 if ([hint isEqualToString:
@"creditCardNumber"]) {
267 return UITextContentTypeCreditCardNumber;
270 if ([hint isEqualToString:
@"email"]) {
271 return UITextContentTypeEmailAddress;
274 if ([hint isEqualToString:
@"jobTitle"]) {
275 return UITextContentTypeJobTitle;
278 if ([hint isEqualToString:
@"givenName"]) {
279 return UITextContentTypeGivenName;
282 if ([hint isEqualToString:
@"middleName"]) {
283 return UITextContentTypeMiddleName;
286 if ([hint isEqualToString:
@"familyName"]) {
287 return UITextContentTypeFamilyName;
290 if ([hint isEqualToString:
@"name"]) {
291 return UITextContentTypeName;
294 if ([hint isEqualToString:
@"namePrefix"]) {
295 return UITextContentTypeNamePrefix;
298 if ([hint isEqualToString:
@"nameSuffix"]) {
299 return UITextContentTypeNameSuffix;
302 if ([hint isEqualToString:
@"nickname"]) {
303 return UITextContentTypeNickname;
306 if ([hint isEqualToString:
@"organizationName"]) {
307 return UITextContentTypeOrganizationName;
310 if ([hint isEqualToString:
@"telephoneNumber"]) {
311 return UITextContentTypeTelephoneNumber;
314 if ([hint isEqualToString:
@"password"]) {
315 return UITextContentTypePassword;
318 if ([hint isEqualToString:
@"oneTimeCode"]) {
319 return UITextContentTypeOneTimeCode;
322 if ([hint isEqualToString:
@"newPassword"]) {
323 return UITextContentTypeNewPassword;
391typedef NS_ENUM(NSInteger, FlutterAutofillType) {
395 kFlutterAutofillTypeNone,
396 kFlutterAutofillTypeRegular,
397 kFlutterAutofillTypePassword,
407 if (isSecureTextEntry) {
414 if ([contentType isEqualToString:UITextContentTypePassword] ||
415 [contentType isEqualToString:UITextContentTypeUsername]) {
419 if ([contentType isEqualToString:UITextContentTypeNewPassword]) {
429 return kFlutterAutofillTypePassword;
434 return kFlutterAutofillTypePassword;
439 return !autofill || [contentType isEqualToString:
@""] ? kFlutterAutofillTypeNone
440 : kFlutterAutofillTypeRegular;
444 return fabsf(
x -
y) <= delta;
470 CGRect selectionRect,
471 BOOL selectionRectIsRTL,
472 BOOL useTrailingBoundaryOfSelectionRect,
473 CGRect otherSelectionRect,
474 BOOL otherSelectionRectIsRTL,
475 CGFloat verticalPrecision) {
477 if (CGRectContainsPoint(
479 selectionRect.origin.x + ((useTrailingBoundaryOfSelectionRect ^ selectionRectIsRTL)
480 ? 0.5 * selectionRect.size.width
482 selectionRect.origin.y, 0.5 * selectionRect.size.width, selectionRect.size.height),
487 CGPoint pointForSelectionRect = CGPointMake(
488 selectionRect.origin.x +
489 (selectionRectIsRTL ^ useTrailingBoundaryOfSelectionRect ? selectionRect.size.width : 0),
490 selectionRect.origin.y + selectionRect.size.height * 0.5);
491 float yDist = fabs(pointForSelectionRect.y - point.y);
492 float xDist = fabs(pointForSelectionRect.x - point.x);
495 CGPoint pointForOtherSelectionRect = CGPointMake(
496 otherSelectionRect.origin.x + (otherSelectionRectIsRTL ? otherSelectionRect.size.width : 0),
497 otherSelectionRect.origin.y + otherSelectionRect.size.height * 0.5);
498 float yDistOther = fabs(pointForOtherSelectionRect.y - point.y);
499 float xDistOther = fabs(pointForOtherSelectionRect.x - point.x);
504 BOOL isCloserVertically = yDist < yDistOther - verticalPrecision;
506 BOOL isAboveBottomOfLine = point.y <= selectionRect.origin.y + selectionRect.size.height;
507 BOOL isCloserHorizontally = xDist < xDistOther;
508 BOOL isBelowBottomOfLine = point.y > selectionRect.origin.y + selectionRect.size.height;
511 if (selectionRectIsRTL) {
512 isFarther = selectionRect.origin.x < otherSelectionRect.origin.x;
514 isFarther = selectionRect.origin.x +
515 (useTrailingBoundaryOfSelectionRect ? selectionRect.size.width : 0) >
516 otherSelectionRect.origin.x;
518 return (isCloserVertically ||
519 (isEqualVertically &&
520 ((isAboveBottomOfLine && isCloserHorizontally) || (isBelowBottomOfLine && isFarther))));
523#pragma mark - FlutterTextPosition
527+ (instancetype)positionWithIndex:(NSUInteger)index {
528 return [[
FlutterTextPosition alloc] initWithIndex:index affinity:UITextStorageDirectionForward];
531+ (instancetype)positionWithIndex:(NSUInteger)index affinity:(UITextStorageDirection)affinity {
535- (instancetype)initWithIndex:(NSUInteger)index affinity:(UITextStorageDirection)affinity {
546#pragma mark - FlutterTextRange
550+ (instancetype)rangeWithNSRange:(NSRange)range {
554- (instancetype)initWithNSRange:(NSRange)range {
562- (UITextPosition*)
start {
564 affinity:UITextStorageDirectionForward];
567- (UITextPosition*)
end {
569 affinity:UITextStorageDirectionBackward];
573 return self.range.length == 0;
576- (
id)copyWithZone:(NSZone*)zone {
581 return NSEqualRanges(
self.range, other.
range);
585#pragma mark - FlutterTokenizer
595- (instancetype)initWithTextInput:(UIResponder<UITextInput>*)textInput {
597 @"The FlutterTokenizer can only be used in a FlutterTextInputView");
598 self = [
super initWithTextInput:textInput];
605- (UITextRange*)rangeEnclosingPosition:(UITextPosition*)position
606 withGranularity:(UITextGranularity)granularity
607 inDirection:(UITextDirection)direction {
609 switch (granularity) {
610 case UITextGranularityLine:
613 result = [
self lineEnclosingPosition:position inDirection:direction];
615 case UITextGranularityCharacter:
616 case UITextGranularityWord:
617 case UITextGranularitySentence:
618 case UITextGranularityParagraph:
619 case UITextGranularityDocument:
621 result = [
super rangeEnclosingPosition:position
622 withGranularity:granularity
623 inDirection:direction];
629- (UITextRange*)lineEnclosingPosition:(UITextPosition*)position
630 inDirection:(UITextDirection)direction {
632 if (@available(iOS 17.0, *)) {
637 if (flutterPosition.
index > _textInputView.text.length ||
638 (flutterPosition.
index == _textInputView.text.length &&
639 direction == UITextStorageDirectionForward)) {
645 NSString* textAfter = [_textInputView
646 textInRange:[_textInputView textRangeFromPosition:position
647 toPosition:[_textInputView endOfDocument]]];
648 NSArray<NSString*>* linesAfter = [textAfter componentsSeparatedByString:@"\n"];
649 NSInteger offSetToLineBreak = [linesAfter firstObject].length;
650 UITextPosition* lineBreakAfter = [_textInputView positionFromPosition:position
651 offset:offSetToLineBreak];
653 NSString* textBefore = [_textInputView
654 textInRange:[_textInputView textRangeFromPosition:[_textInputView beginningOfDocument]
655 toPosition:position]];
656 NSArray<NSString*>* linesBefore = [textBefore componentsSeparatedByString:@"\n"];
657 NSInteger offSetFromLineBreak = [linesBefore lastObject].length;
658 UITextPosition* lineBreakBefore = [_textInputView positionFromPosition:position
659 offset:-offSetFromLineBreak];
661 return [_textInputView textRangeFromPosition:lineBreakBefore toPosition:lineBreakAfter];
666#pragma mark - FlutterTextSelectionRect
671@synthesize rect = _rect;
677+ (instancetype)selectionRectWithRectAndInfo:(CGRect)rect
678 position:(NSUInteger)position
679 writingDirection:(NSWritingDirection)writingDirection
680 containsStart:(
BOOL)containsStart
681 containsEnd:(
BOOL)containsEnd
682 isVertical:(
BOOL)isVertical {
685 writingDirection:writingDirection
686 containsStart:containsStart
687 containsEnd:containsEnd
688 isVertical:isVertical];
691+ (instancetype)selectionRectWithRect:(CGRect)rect position:(NSUInteger)position {
694 writingDirection:NSWritingDirectionNatural
700+ (instancetype)selectionRectWithRect:(CGRect)rect
701 position:(NSUInteger)position
702 writingDirection:(NSWritingDirection)writingDirection {
705 writingDirection:writingDirection
711- (instancetype)initWithRectAndInfo:(CGRect)rect
712 position:(NSUInteger)position
713 writingDirection:(NSWritingDirection)writingDirection
714 containsStart:(
BOOL)containsStart
715 containsEnd:(
BOOL)containsEnd
716 isVertical:(
BOOL)isVertical {
730 return _writingDirection == NSWritingDirectionRightToLeft;
735#pragma mark - FlutterTextPlaceholder
739- (NSArray<UITextSelectionRect*>*)rects {
755@property(nonatomic, retain, readonly) UITextField*
textField;
759 UITextField* _textField;
764 _textField = [[UITextField alloc] init];
769- (
BOOL)isKindOfClass:(Class)aClass {
770 return [
super isKindOfClass:aClass] || (aClass == [UITextField class]);
773- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector {
774 NSMethodSignature* signature = [
super methodSignatureForSelector:aSelector];
776 signature = [
self.textField methodSignatureForSelector:aSelector];
781- (void)forwardInvocation:(NSInvocation*)anInvocation {
782 [anInvocation invokeWithTarget:self.textField];
788@property(nonatomic, readonly, weak) id<FlutterTextInputDelegate> textInputDelegate;
789@property(nonatomic, readonly) UIView* hostView;
794@property(nonatomic, copy) NSString* autofillId;
795@property(nonatomic, readonly) CATransform3D editableTransform;
796@property(nonatomic, assign) CGRect markedRect;
798@property(nonatomic, assign)
BOOL preventCursorDismissWhenResignFirstResponder;
799@property(nonatomic)
BOOL isVisibleToAutofill;
800@property(nonatomic, assign)
BOOL accessibilityEnabled;
801@property(nonatomic, assign)
int textInputClient;
805@property(nonatomic, copy) NSString* temporarilyDeletedComposedCharacter;
806@property(nonatomic, assign) CGRect editMenuTargetRect;
807@property(nonatomic, strong) NSArray<NSDictionary*>* editMenuItems;
809- (void)setEditableTransform:(NSArray*)matrix;
813 int _textInputClient;
830@synthesize tokenizer = _tokenizer;
833 self = [
super initWithFrame:CGRectZero];
836 _textInputClient = 0;
838 _preventCursorDismissWhenResignFirstResponder = NO;
841 _text = [[NSMutableString alloc] init];
846 _pendingDeltas = [[NSMutableArray alloc] init];
849 _editableTransform = CATransform3D();
852 _autocapitalizationType = UITextAutocapitalizationTypeSentences;
853 _autocorrectionType = UITextAutocorrectionTypeDefault;
854 _spellCheckingType = UITextSpellCheckingTypeDefault;
855 _enablesReturnKeyAutomatically = NO;
856 _keyboardAppearance = UIKeyboardAppearanceDefault;
857 _keyboardType = UIKeyboardTypeDefault;
858 _returnKeyType = UIReturnKeyDone;
859 _secureTextEntry = NO;
860 _enableDeltaModel = NO;
862 _accessibilityEnabled = NO;
863 _smartQuotesType = UITextSmartQuotesTypeYes;
864 _smartDashesType = UITextSmartDashesTypeYes;
865 _selectionRects = [[NSArray alloc] init];
867 if (@available(iOS 14.0, *)) {
868 UIScribbleInteraction* interaction = [[UIScribbleInteraction alloc] initWithDelegate:self];
869 [
self addInteraction:interaction];
873 if (@available(iOS 16.0, *)) {
874 _editMenuInteraction = [[UIEditMenuInteraction alloc] initWithDelegate:self];
875 [
self addInteraction:_editMenuInteraction];
881- (void)handleSearchWebAction {
882 [
self.textInputDelegate flutterTextInputView:self
883 searchWebWithSelectedText:[
self textInRange:_selectedTextRange]];
886- (void)handleLookUpAction {
887 [
self.textInputDelegate flutterTextInputView:self
888 lookUpSelectedText:[
self textInRange:_selectedTextRange]];
891- (void)handleShareAction {
892 [
self.textInputDelegate flutterTextInputView:self
893 shareSelectedText:[
self textInRange:_selectedTextRange]];
897- (UICommand*)searchCommandWithSelector:(
SEL)selector
898 element:(UIMenuElement*)element API_AVAILABLE(ios(16.0)) {
899 if ([element isKindOfClass:UICommand.class]) {
900 UICommand* command = (UICommand*)element;
901 return command.action == selector ? command : nil;
902 }
else if ([element isKindOfClass:UIMenu.class]) {
903 NSArray<UIMenuElement*>* children = ((UIMenu*)element).children;
904 for (UIMenuElement* child in children) {
905 UICommand* result = [
self searchCommandWithSelector:selector element:child];
916- (void)addBasicEditingCommandToItems:(NSMutableArray*)items
918 selector:(
SEL)selector
919 suggestedMenu:(UIMenu*)suggestedMenu {
920 UICommand* command = [
self searchCommandWithSelector:selector element:suggestedMenu];
922 [items addObject:command];
924 NSString* errorMessage =
925 [NSString stringWithFormat:@"Cannot find context menu item of type \"%@\".", type];
926 [FlutterLogger logError:errorMessage];
930- (void)addAdditionalBasicCommandToItems:(NSMutableArray*)items
932 selector:(
SEL)selector
933 encodedItem:(NSDictionary<NSString*,
id>*)encodedItem {
934 NSString* title = encodedItem[@"title"];
936 UICommand* command = [UICommand commandWithTitle:title
940 [items addObject:command];
942 NSString* errorMessage =
943 [NSString stringWithFormat:@"Missing title for context menu item of type \"%@\".", type];
944 [FlutterLogger logError:errorMessage];
948- (UIMenu*)editMenuInteraction:(UIEditMenuInteraction*)interaction
949 menuForConfiguration:(UIEditMenuConfiguration*)configuration
950 suggestedActions:(NSArray<UIMenuElement*>*)suggestedActions API_AVAILABLE(ios(16.0)) {
951 UIMenu* suggestedMenu = [UIMenu menuWithChildren:suggestedActions];
952 if (!_editMenuItems) {
953 return suggestedMenu;
956 NSMutableArray* items = [NSMutableArray array];
957 for (NSDictionary<NSString*, id>* encodedItem in _editMenuItems) {
958 NSString*
type = encodedItem[@"type"];
959 if ([
type isEqualToString:
@"copy"]) {
960 [
self addBasicEditingCommandToItems:items
962 selector:@selector(copy:)
963 suggestedMenu:suggestedMenu];
964 }
else if ([
type isEqualToString:
@"paste"]) {
965 [
self addBasicEditingCommandToItems:items
967 selector:@selector(paste:)
968 suggestedMenu:suggestedMenu];
969 }
else if ([
type isEqualToString:
@"cut"]) {
970 [
self addBasicEditingCommandToItems:items
972 selector:@selector(cut:)
973 suggestedMenu:suggestedMenu];
974 }
else if ([
type isEqualToString:
@"delete"]) {
975 [
self addBasicEditingCommandToItems:items
977 selector:@selector(delete:)
978 suggestedMenu:suggestedMenu];
979 }
else if ([
type isEqualToString:
@"selectAll"]) {
980 [
self addBasicEditingCommandToItems:items
982 selector:@selector(selectAll:)
983 suggestedMenu:suggestedMenu];
984 }
else if ([
type isEqualToString:
@"searchWeb"]) {
985 [
self addAdditionalBasicCommandToItems:items
987 selector:@selector(handleSearchWebAction)
988 encodedItem:encodedItem];
989 }
else if ([
type isEqualToString:
@"share"]) {
990 [
self addAdditionalBasicCommandToItems:items
992 selector:@selector(handleShareAction)
993 encodedItem:encodedItem];
994 }
else if ([
type isEqualToString:
@"lookUp"]) {
995 [
self addAdditionalBasicCommandToItems:items
997 selector:@selector(handleLookUpAction)
998 encodedItem:encodedItem];
999 }
else if ([
type isEqualToString:
@"captureTextFromCamera"]) {
1000 if (@available(iOS 15.0, *)) {
1001 [
self addBasicEditingCommandToItems:items
1003 selector:@selector(captureTextFromCamera:)
1004 suggestedMenu:suggestedMenu];
1006 }
else if ([
type isEqualToString:
@"custom"]) {
1007 NSString* callbackId = encodedItem[@"id"];
1008 NSString* title = encodedItem[@"title"];
1009 if (callbackId && title) {
1011 UIAction*
action = [UIAction
1012 actionWithTitle:title
1015 handler:^(__kindof UIAction* _Nonnull action) {
1018 [strongSelf.textInputDelegate flutterTextInputView:strongSelf
1019 performContextMenuCustomActionWithActionID:callbackId
1020 textInputClient:strongSelf->
1024 [items addObject:action];
1028 return [UIMenu menuWithChildren:items];
1031- (void)editMenuInteraction:(UIEditMenuInteraction*)interaction
1032 willDismissMenuForConfiguration:(UIEditMenuConfiguration*)configuration
1033 animator:(
id<UIEditMenuInteractionAnimating>)animator
1034 API_AVAILABLE(ios(16.0)) {
1035 [
self.textInputDelegate flutterTextInputView:self
1036 willDismissEditMenuWithTextInputClient:_textInputClient];
1039- (CGRect)editMenuInteraction:(UIEditMenuInteraction*)interaction
1040 targetRectForConfiguration:(UIEditMenuConfiguration*)configuration API_AVAILABLE(ios(16.0)) {
1041 return _editMenuTargetRect;
1044- (void)showEditMenuWithTargetRect:(CGRect)targetRect
1045 items:(NSArray<NSDictionary*>*)items API_AVAILABLE(ios(16.0)) {
1046 _editMenuTargetRect = targetRect;
1047 _editMenuItems = items;
1049 UIEditMenuConfiguration* config =
1050 [UIEditMenuConfiguration configurationWithIdentifier:nil sourcePoint:CGPointZero];
1051 [
self.editMenuInteraction presentEditMenuWithConfiguration:config];
1055 [
self.editMenuInteraction dismissMenu];
1058- (void)configureWithDictionary:(NSDictionary*)configuration {
1059 NSDictionary* inputType = configuration[kKeyboardType];
1061 NSDictionary* autofill = configuration[kAutofillProperties];
1063 self.secureTextEntry = [configuration[kSecureTextEntry] boolValue];
1064 self.enableDeltaModel = [configuration[kEnableDeltaModel] boolValue];
1071 NSString* smartDashesType = configuration[kSmartDashesType];
1073 bool smartDashesIsDisabled = smartDashesType && [smartDashesType isEqualToString:@"0"];
1074 self.smartDashesType = smartDashesIsDisabled ? UITextSmartDashesTypeNo : UITextSmartDashesTypeYes;
1075 NSString* smartQuotesType = configuration[kSmartQuotesType];
1077 bool smartQuotesIsDisabled = smartQuotesType && [smartQuotesType isEqualToString:@"0"];
1078 self.smartQuotesType = smartQuotesIsDisabled ? UITextSmartQuotesTypeNo : UITextSmartQuotesTypeYes;
1080 self.keyboardAppearance = UIKeyboardAppearanceDark;
1082 self.keyboardAppearance = UIKeyboardAppearanceLight;
1084 self.keyboardAppearance = UIKeyboardAppearanceDefault;
1086 NSString* autocorrect = configuration[kAutocorrectionType];
1087 bool autocorrectIsDisabled = autocorrect && ![autocorrect boolValue];
1088 self.autocorrectionType =
1089 autocorrectIsDisabled ? UITextAutocorrectionTypeNo : UITextAutocorrectionTypeDefault;
1090 self.spellCheckingType =
1091 autocorrectIsDisabled ? UITextSpellCheckingTypeNo : UITextSpellCheckingTypeDefault;
1093 if (autofill == nil) {
1094 self.textContentType =
@"";
1097 [
self setTextInputState:autofill[kAutofillEditingValue]];
1098 NSAssert(_autofillId,
@"The autofill configuration must contain an autofill id");
1102 self.isVisibleToAutofill = autofill || _secureTextEntry;
1105- (UITextContentType)textContentType {
1106 return _textContentType;
1119- (UIColor*)insertionPointColor {
1120 return [UIColor clearColor];
1123- (UIColor*)selectionBarColor {
1124 return [UIColor clearColor];
1127- (UIColor*)selectionHighlightColor {
1128 return [UIColor clearColor];
1131- (UIInputViewController*)inputViewController {
1142- (
id<FlutterTextInputDelegate>)textInputDelegate {
1146- (
BOOL)respondsToSelector:(
SEL)selector {
1147 if (@available(iOS 17.0, *)) {
1149 if (selector ==
@selector(insertionPointColor)) {
1153 return [
super respondsToSelector:selector];
1156- (void)setTextInputClient:(
int)client {
1157 _textInputClient = client;
1161- (UITextInteraction*)textInteraction
API_AVAILABLE(ios(13.0)) {
1162 if (!_textInteraction) {
1163 _textInteraction = [UITextInteraction textInteractionForMode:UITextInteractionModeEditable];
1164 _textInteraction.textInput =
self;
1166 return _textInteraction;
1169- (void)setTextInputState:(NSDictionary*)state {
1175 if (!
self.inputDelegate &&
self.isFirstResponder) {
1176 [
self addInteraction:self.textInteraction];
1179 NSString* newText = state[@"text"];
1180 BOOL textChanged = ![
self.text isEqualToString:newText];
1182 [
self.inputDelegate textWillChange:self];
1183 [
self.text setString:newText];
1185 NSInteger composingBase = [state[@"composingBase"] intValue];
1186 NSInteger composingExtent = [state[@"composingExtent"] intValue];
1187 NSRange composingRange = [
self clampSelection:NSMakeRange(MIN(composingBase, composingExtent),
1188 ABS(composingBase - composingExtent))
1191 self.markedTextRange =
1194 NSRange selectedRange = [
self clampSelectionFromBase:[state[@"selectionBase"] intValue]
1195 extent:[state[@"selectionExtent"] intValue]
1198 NSRange oldSelectedRange = [(
FlutterTextRange*)
self.selectedTextRange range];
1199 if (!NSEqualRanges(selectedRange, oldSelectedRange)) {
1200 [
self.inputDelegate selectionWillChange:self];
1208 [
self.inputDelegate selectionDidChange:self];
1212 [
self.inputDelegate textDidChange:self];
1215 if (_textInteraction) {
1216 [
self removeInteraction:_textInteraction];
1221- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
1222 _scribbleFocusStatus = FlutterScribbleFocusStatusUnfocused;
1223 [
self resetScribbleInteractionStatusIfEnding];
1224 [
self.viewResponder touchesBegan:touches withEvent:event];
1227- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
1228 [
self.viewResponder touchesMoved:touches withEvent:event];
1231- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1232 [
self.viewResponder touchesEnded:touches withEvent:event];
1235- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1236 [
self.viewResponder touchesCancelled:touches withEvent:event];
1239- (void)touchesEstimatedPropertiesUpdated:(NSSet*)touches {
1240 [
self.viewResponder touchesEstimatedPropertiesUpdated:touches];
1250- (NSRange)clampSelectionFromBase:(
int)selectionBase
1251 extent:(
int)selectionExtent
1252 forText:(NSString*)text {
1253 int loc = MIN(selectionBase, selectionExtent);
1254 int len = ABS(selectionExtent - selectionBase);
1255 return loc < 0 ? NSMakeRange(0, 0)
1256 : [
self clampSelection:NSMakeRange(loc, len) forText:
self.
text];
1259- (NSRange)clampSelection:(NSRange)range forText:(NSString*)text {
1260 NSUInteger
start = MIN(MAX(range.location, 0),
text.length);
1261 NSUInteger
length = MIN(range.length,
text.length - start);
1262 return NSMakeRange(start,
length);
1265- (
BOOL)isVisibleToAutofill {
1266 return self.frame.size.width > 0 &&
self.frame.size.height > 0;
1274- (void)setIsVisibleToAutofill:(
BOOL)isVisibleToAutofill {
1277 self.frame = isVisibleToAutofill ? CGRectMake(0, 0, 1, 1) : CGRectZero;
1280#pragma mark UIScribbleInteractionDelegate
1285 if (@available(iOS 14.0, *)) {
1286 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
1293- (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction*)interaction
1294 API_AVAILABLE(ios(14.0)) {
1296 [
self.textInputDelegate flutterTextInputViewScribbleInteractionBegan:self];
1299- (void)scribbleInteractionDidFinishWriting:(UIScribbleInteraction*)interaction
1300 API_AVAILABLE(ios(14.0)) {
1302 [
self.textInputDelegate flutterTextInputViewScribbleInteractionFinished:self];
1305- (
BOOL)scribbleInteraction:(UIScribbleInteraction*)interaction
1306 shouldBeginAtLocation:(CGPoint)location API_AVAILABLE(ios(14.0)) {
1310- (
BOOL)scribbleInteractionShouldDelayFocus:(UIScribbleInteraction*)interaction
1311 API_AVAILABLE(ios(14.0)) {
1315#pragma mark - UIResponder Overrides
1317- (
BOOL)canBecomeFirstResponder {
1322 return _textInputClient != 0;
1325- (
BOOL)resignFirstResponder {
1326 BOOL success = [
super resignFirstResponder];
1328 if (!_preventCursorDismissWhenResignFirstResponder) {
1329 [
self.textInputDelegate flutterTextInputView:self
1330 didResignFirstResponderWithTextInputClient:_textInputClient];
1336- (
BOOL)canPerformAction:(
SEL)action withSender:(
id)sender {
1337 if (
action ==
@selector(paste:)) {
1339 return [UIPasteboard generalPasteboard].hasStrings;
1340 }
else if (
action ==
@selector(copy:) ||
action == @selector(cut:) ||
1341 action == @selector(delete:)) {
1342 return [
self textInRange:_selectedTextRange].length > 0;
1343 }
else if (
action ==
@selector(selectAll:)) {
1344 return self.hasText;
1345 }
else if (
action ==
@selector(captureTextFromCamera:)) {
1346 if (@available(iOS 15.0, *)) {
1351 return [
super canPerformAction:action withSender:sender];
1354#pragma mark - UIResponderStandardEditActions Overrides
1356- (void)cut:(
id)sender {
1357 [UIPasteboard generalPasteboard].string = [
self textInRange:_selectedTextRange];
1358 [
self replaceRange:_selectedTextRange withText:@""];
1361- (void)copy:(
id)sender {
1362 [UIPasteboard generalPasteboard].string = [
self textInRange:_selectedTextRange];
1365- (void)paste:(
id)sender {
1366 NSString* pasteboardString = [UIPasteboard generalPasteboard].string;
1367 if (pasteboardString != nil) {
1368 [
self insertText:pasteboardString];
1372- (void)delete:(
id)sender {
1373 [
self replaceRange:_selectedTextRange withText:@""];
1376- (void)selectAll:(
id)sender {
1377 [
self setSelectedTextRange:[
self textRangeFromPosition:[
self beginningOfDocument]
1378 toPosition:[
self endOfDocument]]];
1381#pragma mark - UITextInput Overrides
1383- (
id<UITextInputTokenizer>)tokenizer {
1384 if (_tokenizer == nil) {
1391 return [_selectedTextRange copy];
1395- (void)setSelectedTextRangeLocal:(UITextRange*)selectedTextRange {
1400 rangeWithNSRange:fml::RangeForCharactersInRange(self.text, flutterTextRange.range)] copy];
1407- (void)setSelectedTextRange:(UITextRange*)selectedTextRange {
1412 [
self setSelectedTextRangeLocal:selectedTextRange];
1414 if (_enableDeltaModel) {
1415 [
self updateEditingStateWithDelta:flutter::TextEditingDelta([
self.text UTF8String])];
1417 [
self updateEditingState];
1421 _scribbleFocusStatus == FlutterScribbleFocusStatusFocused) {
1425 if (flutterTextRange.
range.length > 0) {
1426 [
self.textInputDelegate flutterTextInputView:self showToolbar:_textInputClient];
1430 [
self resetScribbleInteractionStatusIfEnding];
1433- (
id)insertDictationResultPlaceholder {
1437- (void)removeDictationResultPlaceholder:(
id)placeholder willInsertResult:(
BOOL)willInsertResult {
1440- (NSString*)textInRange:(UITextRange*)range {
1445 @"Expected a FlutterTextRange for range (got %@).", [range class]);
1447 if (textRange.location == NSNotFound) {
1456 NSUInteger location = MIN(textRange.location,
self.text.length);
1457 NSUInteger
length = MIN(
self.text.length - location, textRange.length);
1458 NSRange safeRange = NSMakeRange(location,
length);
1459 return [
self.text substringWithRange:safeRange];
1464- (void)replaceRangeLocal:(NSRange)range withText:(NSString*)text {
1465 [
self.text replaceCharactersInRange:[
self clampSelection:range forText:self.text]
1471 const NSRange newSelectionRange =
1472 [
self clampSelection:NSMakeRange(range.location + text.length, 0) forText:self.text];
1475 self.markedTextRange = nil;
1478- (void)replaceRange:(UITextRange*)range withText:(NSString*)text {
1479 NSString* textBeforeChange = [
self.text copy];
1481 [
self replaceRangeLocal:replaceRange withText:text];
1482 if (_enableDeltaModel) {
1483 NSRange nextReplaceRange = [
self clampSelection:replaceRange forText:textBeforeChange];
1484 [
self updateEditingStateWithDelta:flutter::TextEditingDelta(
1485 [textBeforeChange UTF8String],
1487 nextReplaceRange.location,
1488 nextReplaceRange.location + nextReplaceRange.length),
1489 [text UTF8String])];
1491 [
self updateEditingState];
1495- (
BOOL)shouldChangeTextInRange:(UITextRange*)range replacementText:(NSString*)text {
1498 self.temporarilyDeletedComposedCharacter = nil;
1500 if (
self.returnKeyType == UIReturnKeyDefault && [
text isEqualToString:
@"\n"]) {
1501 [
self.textInputDelegate flutterTextInputView:self
1502 performAction:FlutterTextInputActionNewline
1503 withClient:_textInputClient];
1507 if ([
text isEqualToString:
@"\n"]) {
1508 FlutterTextInputAction
action;
1509 switch (
self.returnKeyType) {
1510 case UIReturnKeyDefault:
1511 action = FlutterTextInputActionUnspecified;
1513 case UIReturnKeyDone:
1514 action = FlutterTextInputActionDone;
1517 action = FlutterTextInputActionGo;
1519 case UIReturnKeySend:
1520 action = FlutterTextInputActionSend;
1522 case UIReturnKeySearch:
1523 case UIReturnKeyGoogle:
1524 case UIReturnKeyYahoo:
1525 action = FlutterTextInputActionSearch;
1527 case UIReturnKeyNext:
1528 action = FlutterTextInputActionNext;
1530 case UIReturnKeyContinue:
1531 action = FlutterTextInputActionContinue;
1533 case UIReturnKeyJoin:
1534 action = FlutterTextInputActionJoin;
1536 case UIReturnKeyRoute:
1537 action = FlutterTextInputActionRoute;
1539 case UIReturnKeyEmergencyCall:
1540 action = FlutterTextInputActionEmergencyCall;
1544 [
self.textInputDelegate flutterTextInputView:self
1545 performAction:action
1546 withClient:_textInputClient];
1555- (void)setMarkedText:(NSString*)markedText selectedRange:(NSRange)markedSelectedRange {
1556 NSString* textBeforeChange = [
self.text copy];
1559 _scribbleFocusStatus != FlutterScribbleFocusStatusUnfocused) {
1563 if (markedText == nil) {
1568 const NSRange& actualReplacedRange = currentMarkedTextRange && !currentMarkedTextRange.isEmpty
1569 ? currentMarkedTextRange.range
1573 [
self.text replaceCharactersInRange:actualReplacedRange withString:markedText];
1575 const NSRange newMarkedRange = NSMakeRange(actualReplacedRange.location, markedText.length);
1576 self.markedTextRange =
1579 [
self setSelectedTextRangeLocal:
1581 rangeWithNSRange:[
self clampSelection:NSMakeRange(markedSelectedRange.location +
1582 newMarkedRange.location,
1583 markedSelectedRange.length)
1584 forText:self.text]]];
1585 if (_enableDeltaModel) {
1586 NSRange nextReplaceRange = [
self clampSelection:actualReplacedRange forText:textBeforeChange];
1587 [
self updateEditingStateWithDelta:flutter::TextEditingDelta(
1588 [textBeforeChange UTF8String],
1590 nextReplaceRange.location,
1591 nextReplaceRange.location + nextReplaceRange.length),
1592 [markedText UTF8String])];
1594 [
self updateEditingState];
1599 if (!
self.markedTextRange) {
1602 self.markedTextRange = nil;
1603 if (_enableDeltaModel) {
1604 [
self updateEditingStateWithDelta:flutter::TextEditingDelta([
self.text UTF8String])];
1606 [
self updateEditingState];
1610- (UITextRange*)textRangeFromPosition:(UITextPosition*)fromPosition
1611 toPosition:(UITextPosition*)toPosition {
1614 if (toIndex >= fromIndex) {
1627- (NSUInteger)decrementOffsetPosition:(NSUInteger)position {
1631- (NSUInteger)incrementOffsetPosition:(NSUInteger)position {
1633 return MIN(position + charRange.length,
self.text.length);
1636- (UITextPosition*)positionFromPosition:(UITextPosition*)position offset:(NSInteger)offset {
1639 NSInteger newLocation = (NSInteger)offsetPosition + offset;
1640 if (newLocation < 0 || newLocation > (NSInteger)
self.text.length) {
1649 for (NSInteger
i = 0;
i < offset && offsetPosition <
self.text.length; ++
i) {
1650 offsetPosition = [
self incrementOffsetPosition:offsetPosition];
1653 for (NSInteger
i = 0;
i < ABS(offset) && offsetPosition > 0; ++
i) {
1654 offsetPosition = [
self decrementOffsetPosition:offsetPosition];
1660- (UITextPosition*)positionFromPosition:(UITextPosition*)position
1661 inDirection:(UITextLayoutDirection)direction
1662 offset:(NSInteger)offset {
1664 switch (direction) {
1665 case UITextLayoutDirectionLeft:
1666 case UITextLayoutDirectionUp:
1667 return [
self positionFromPosition:position offset:offset * -1];
1668 case UITextLayoutDirectionRight:
1669 case UITextLayoutDirectionDown:
1670 return [
self positionFromPosition:position offset:1];
1674- (UITextPosition*)beginningOfDocument {
1678- (UITextPosition*)endOfDocument {
1680 affinity:UITextStorageDirectionBackward];
1683- (NSComparisonResult)comparePosition:(UITextPosition*)position toPosition:(UITextPosition*)other {
1686 if (positionIndex < otherIndex) {
1687 return NSOrderedAscending;
1689 if (positionIndex > otherIndex) {
1690 return NSOrderedDescending;
1694 if (positionAffinity == otherAffinity) {
1695 return NSOrderedSame;
1697 if (positionAffinity == UITextStorageDirectionBackward) {
1699 return NSOrderedAscending;
1702 return NSOrderedDescending;
1705- (NSInteger)offsetFromPosition:(UITextPosition*)from toPosition:(UITextPosition*)toPosition {
1709- (UITextPosition*)positionWithinRange:(UITextRange*)range
1710 farthestInDirection:(UITextLayoutDirection)direction {
1712 UITextStorageDirection affinity;
1713 switch (direction) {
1714 case UITextLayoutDirectionLeft:
1715 case UITextLayoutDirectionUp:
1717 affinity = UITextStorageDirectionForward;
1719 case UITextLayoutDirectionRight:
1720 case UITextLayoutDirectionDown:
1722 affinity = UITextStorageDirectionBackward;
1728- (UITextRange*)characterRangeByExtendingPosition:(UITextPosition*)position
1729 inDirection:(UITextLayoutDirection)direction {
1731 NSUInteger startIndex;
1732 NSUInteger endIndex;
1733 switch (direction) {
1734 case UITextLayoutDirectionLeft:
1735 case UITextLayoutDirectionUp:
1736 startIndex = [
self decrementOffsetPosition:positionIndex];
1737 endIndex = positionIndex;
1739 case UITextLayoutDirectionRight:
1740 case UITextLayoutDirectionDown:
1741 startIndex = positionIndex;
1742 endIndex = [
self incrementOffsetPosition:positionIndex];
1748#pragma mark - UITextInput text direction handling
1750- (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition*)position
1751 inDirection:(UITextStorageDirection)direction {
1753 return UITextWritingDirectionNatural;
1756- (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection
1757 forRange:(UITextRange*)range {
1761#pragma mark - UITextInput cursor, selection rect handling
1763- (void)setMarkedRect:(CGRect)markedRect {
1764 _markedRect = markedRect;
1771- (void)setEditableTransform:(NSArray*)matrix {
1772 CATransform3D*
transform = &_editableTransform;
1774 transform->m11 = [matrix[0] doubleValue];
1775 transform->m12 = [matrix[1] doubleValue];
1776 transform->m13 = [matrix[2] doubleValue];
1777 transform->m14 = [matrix[3] doubleValue];
1779 transform->m21 = [matrix[4] doubleValue];
1780 transform->m22 = [matrix[5] doubleValue];
1781 transform->m23 = [matrix[6] doubleValue];
1782 transform->m24 = [matrix[7] doubleValue];
1784 transform->m31 = [matrix[8] doubleValue];
1785 transform->m32 = [matrix[9] doubleValue];
1786 transform->m33 = [matrix[10] doubleValue];
1787 transform->m34 = [matrix[11] doubleValue];
1789 transform->m41 = [matrix[12] doubleValue];
1790 transform->m42 = [matrix[13] doubleValue];
1791 transform->m43 = [matrix[14] doubleValue];
1792 transform->m44 = [matrix[15] doubleValue];
1802 incomingRect.origin,
1803 CGPointMake(incomingRect.origin.x, incomingRect.origin.y + incomingRect.size.height),
1804 CGPointMake(incomingRect.origin.x + incomingRect.size.width, incomingRect.origin.y),
1805 CGPointMake(incomingRect.origin.x + incomingRect.size.width,
1806 incomingRect.origin.y + incomingRect.size.height)};
1808 CGPoint origin = CGPointMake(CGFLOAT_MAX, CGFLOAT_MAX);
1809 CGPoint farthest = CGPointMake(-CGFLOAT_MAX, -CGFLOAT_MAX);
1811 for (
int i = 0;
i < 4;
i++) {
1812 const CGPoint point =
points[i];
1814 CGFloat
x = _editableTransform.m11 * point.x + _editableTransform.m21 * point.
y +
1815 _editableTransform.m41;
1816 CGFloat
y = _editableTransform.m12 * point.x + _editableTransform.m22 * point.
y +
1817 _editableTransform.m42;
1819 const CGFloat w = _editableTransform.m14 * point.x + _editableTransform.m24 * point.
y +
1820 _editableTransform.m44;
1824 }
else if (w != 1.0) {
1829 origin.x = MIN(origin.x,
x);
1830 origin.y = MIN(origin.y,
y);
1831 farthest.x = MAX(farthest.x,
x);
1832 farthest.y = MAX(farthest.y,
y);
1834 return CGRectMake(origin.x, origin.y, farthest.x - origin.x, farthest.y - origin.y);
1843- (CGRect)firstRectForRange:(UITextRange*)range {
1845 @"Expected a FlutterTextPosition for range.start (got %@).", [range.
start class]);
1847 @"Expected a FlutterTextPosition for range.end (got %@).", [range.
end class]);
1850 if (_markedTextRange != nil) {
1861 CGRect rect = _markedRect;
1862 if (CGRectIsEmpty(rect)) {
1863 rect = CGRectInset(rect, -0.1, 0);
1869 NSAssert(hostView == nil || [
self isDescendantOfView:hostView],
@"%@ is not a descendant of %@",
1871 return hostView ? [hostView convertRect:_cachedFirstRect toView:self] :
_cachedFirstRect;
1875 _scribbleFocusStatus == FlutterScribbleFocusStatusUnfocused) {
1876 if (@available(iOS 17.0, *)) {
1886 [
self.textInputDelegate flutterTextInputView:self
1887 showAutocorrectionPromptRectForStart:start
1889 withClient:_textInputClient];
1897 if (@available(iOS 17, *)) {
1903 NSUInteger first =
start;
1908 CGRect startSelectionRect = CGRectNull;
1909 CGRect endSelectionRect = CGRectNull;
1912 CGFloat minY = CGFLOAT_MAX;
1913 CGFloat maxY = CGFLOAT_MIN;
1916 rangeWithNSRange:fml::RangeForCharactersInRange(self.text, NSMakeRange(0, self.text.length))];
1917 for (NSUInteger
i = 0;
i < [_selectionRects count];
i++) {
1918 BOOL startsOnOrBeforeStartOfRange = _selectionRects[i].position <= first;
1919 BOOL isLastSelectionRect =
i + 1 == [_selectionRects count];
1920 BOOL endOfTextIsAfterStartOfRange = isLastSelectionRect && textRange.
range.length > first;
1921 BOOL nextSelectionRectIsAfterStartOfRange =
1922 !isLastSelectionRect && _selectionRects[i + 1].position > first;
1923 if (startsOnOrBeforeStartOfRange &&
1924 (endOfTextIsAfterStartOfRange || nextSelectionRectIsAfterStartOfRange)) {
1926 if (@available(iOS 17, *)) {
1927 startSelectionRect = _selectionRects[i].rect;
1929 return _selectionRects[i].rect;
1932 if (!CGRectIsNull(startSelectionRect)) {
1933 minY = fmin(minY, CGRectGetMinY(_selectionRects[
i].rect));
1934 maxY = fmax(maxY, CGRectGetMaxY(_selectionRects[
i].rect));
1935 BOOL endsOnOrAfterEndOfRange = _selectionRects[i].position >=
end - 1;
1936 BOOL nextSelectionRectIsOnNextLine =
1937 !isLastSelectionRect &&
1942 CGRectGetMidY(_selectionRects[
i + 1].rect) > CGRectGetMaxY(_selectionRects[
i].rect);
1943 if (endsOnOrAfterEndOfRange || isLastSelectionRect || nextSelectionRectIsOnNextLine) {
1944 endSelectionRect = _selectionRects[i].rect;
1949 if (CGRectIsNull(startSelectionRect) || CGRectIsNull(endSelectionRect)) {
1953 CGFloat minX = fmin(CGRectGetMinX(startSelectionRect), CGRectGetMinX(endSelectionRect));
1954 CGFloat maxX = fmax(CGRectGetMaxX(startSelectionRect), CGRectGetMaxX(endSelectionRect));
1955 return CGRectMake(minX, minY, maxX - minX, maxY - minY);
1963 NSArray<UITextSelectionRect*>* rects = [
self
1965 rangeWithNSRange:fml::RangeForCharactersInRange(
1969 (index >= (NSInteger)self.text.length)
1972 if (rects.count == 0) {
1978 CGRect characterAfterCaret = rects[0].rect;
1983 return CGRectMake(characterAfterCaret.origin.x + characterAfterCaret.size.width,
1984 characterAfterCaret.origin.y, 0, characterAfterCaret.size.height);
1986 return CGRectMake(characterAfterCaret.origin.x, characterAfterCaret.origin.y, 0,
1987 characterAfterCaret.size.height);
1989 }
else if (rects.count == 2 && affinity == UITextStorageDirectionForward) {
1992 CGRect characterAfterCaret = rects[1].rect;
1997 return CGRectMake(characterAfterCaret.origin.x + characterAfterCaret.size.width,
1998 characterAfterCaret.origin.y, 0, characterAfterCaret.size.height);
2000 return CGRectMake(characterAfterCaret.origin.x, characterAfterCaret.origin.y, 0,
2001 characterAfterCaret.size.height);
2010 CGRect characterBeforeCaret = rects[0].rect;
2013 return CGRectMake(characterBeforeCaret.origin.x, characterBeforeCaret.origin.y, 0,
2014 characterBeforeCaret.size.height);
2016 return CGRectMake(characterBeforeCaret.origin.x + characterBeforeCaret.size.width,
2017 characterBeforeCaret.origin.y, 0, characterBeforeCaret.size.height);
2021- (UITextPosition*)closestPositionToPoint:(CGPoint)point {
2022 if ([_selectionRects count] == 0) {
2024 @"Expected a FlutterTextPosition for position (got %@).",
2027 UITextStorageDirection currentAffinity =
2033 rangeWithNSRange:fml::RangeForCharactersInRange(self.text, NSMakeRange(0, self.text.length))];
2034 return [
self closestPositionToPoint:point withinRange:range];
2037- (NSArray*)selectionRectsForRange:(UITextRange*)range {
2045 @"Expected a FlutterTextPosition for range.start (got %@).", [range.
start class]);
2047 @"Expected a FlutterTextPosition for range.end (got %@).", [range.
end class]);
2050 NSMutableArray* rects = [[NSMutableArray alloc] init];
2051 for (NSUInteger
i = 0;
i < [_selectionRects count];
i++) {
2052 if (_selectionRects[
i].position >= start &&
2053 (_selectionRects[
i].position <
end ||
2054 (start ==
end && _selectionRects[
i].position <=
end))) {
2055 float width = _selectionRects[i].rect.size.width;
2059 CGRect rect = CGRectMake(_selectionRects[
i].rect.origin.x, _selectionRects[
i].rect.origin.y,
2060 width, _selectionRects[
i].rect.size.height);
2063 position:_selectionRects[i].position
2067 self.text, NSMakeRange(0, self.text.length))
2070 [rects addObject:selectionRect];
2076- (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange*)range {
2078 @"Expected a FlutterTextPosition for range.start (got %@).", [range.
start class]);
2080 @"Expected a FlutterTextPosition for range.end (got %@).", [range.
end class]);
2090 NSUInteger _closestRectIndex = 0;
2091 for (NSUInteger
i = 0;
i < [_selectionRects count];
i++) {
2092 NSUInteger position = _selectionRects[i].position;
2093 if (position >= start && position <=
end) {
2096 point, _selectionRects[
i].rect, _selectionRects[
i].isRTL,
2097 NO, _selectionRects[_closestRectIndex].rect,
2098 _selectionRects[_closestRectIndex].isRTL, verticalPrecision)) {
2100 _closestRectIndex =
i;
2107 affinity:UITextStorageDirectionForward];
2113 for (NSUInteger
i = MAX(0, _closestRectIndex - 1);
2114 i < MIN(_closestRectIndex + 2, [_selectionRects count]);
i++) {
2115 NSUInteger position = _selectionRects[i].position + 1;
2116 if (position >= start && position <=
end) {
2118 point, _selectionRects[
i].rect, _selectionRects[
i].isRTL,
2119 YES, _selectionRects[_closestRectIndex].rect,
2120 _selectionRects[_closestRectIndex].isRTL, verticalPrecision)) {
2123 affinity:UITextStorageDirectionBackward];
2128 return closestPosition;
2131- (UITextRange*)characterRangeAtPoint:(CGPoint)point {
2134 return [
FlutterTextRange rangeWithNSRange:fml::RangeForCharacterAtIndex(self.text, currentIndex)];
2165- (void)beginFloatingCursorAtPoint:(CGPoint)point {
2182 [
self.textInputDelegate flutterTextInputView:self
2183 updateFloatingCursor:FlutterFloatingCursorDragStateStart
2184 withClient:_textInputClient
2185 withPosition:@{@"X" : @0, @"Y" : @0}];
2188- (void)updateFloatingCursorAtPoint:(CGPoint)point {
2189 [
self.textInputDelegate flutterTextInputView:self
2190 updateFloatingCursor:FlutterFloatingCursorDragStateUpdate
2191 withClient:_textInputClient
2193 @"X" : @(point.x - _floatingCursorOffset.x),
2194 @"Y" : @(point.y - _floatingCursorOffset.y)
2198- (void)endFloatingCursor {
2200 [
self.textInputDelegate flutterTextInputView:self
2201 updateFloatingCursor:FlutterFloatingCursorDragStateEnd
2202 withClient:_textInputClient
2203 withPosition:@{@"X" : @0, @"Y" : @0}];
2206#pragma mark - UIKeyInput Overrides
2208- (void)updateEditingState {
2213 NSInteger composingBase = -1;
2214 NSInteger composingExtent = -1;
2215 if (
self.markedTextRange != nil) {
2219 NSDictionary* state = @{
2220 @"selectionBase" : @(selectionBase),
2221 @"selectionExtent" : @(selectionExtent),
2223 @"selectionIsDirectional" : @(false),
2224 @"composingBase" : @(composingBase),
2225 @"composingExtent" : @(composingExtent),
2226 @"text" : [NSString stringWithString:
self.
text],
2229 if (_textInputClient == 0 && _autofillId != nil) {
2230 [
self.textInputDelegate flutterTextInputView:self
2231 updateEditingClient:_textInputClient
2233 withTag:_autofillId];
2235 [
self.textInputDelegate flutterTextInputView:self
2236 updateEditingClient:_textInputClient
2241- (void)updateEditingStateWithDelta:(
flutter::TextEditingDelta)delta {
2246 NSInteger composingBase = -1;
2247 NSInteger composingExtent = -1;
2248 if (
self.markedTextRange != nil) {
2253 NSDictionary* deltaToFramework = @{
2254 @"oldText" : @(delta.old_text().c_str()),
2255 @"deltaText" : @(delta.delta_text().c_str()),
2256 @"deltaStart" : @(delta.delta_start()),
2257 @"deltaEnd" : @(delta.delta_end()),
2258 @"selectionBase" : @(selectionBase),
2259 @"selectionExtent" : @(selectionExtent),
2261 @"selectionIsDirectional" : @(false),
2262 @"composingBase" : @(composingBase),
2263 @"composingExtent" : @(composingExtent),
2266 [_pendingDeltas addObject:deltaToFramework];
2268 if (_pendingDeltas.count == 1) {
2270 dispatch_async(dispatch_get_main_queue(), ^{
2272 if (strongSelf && strongSelf.pendingDeltas.count > 0) {
2273 NSDictionary* deltas = @{
2274 @"deltas" : strongSelf.pendingDeltas,
2277 [strongSelf.textInputDelegate flutterTextInputView:strongSelf
2278 updateEditingClient:strongSelf->_textInputClient
2280 [strongSelf.pendingDeltas removeAllObjects];
2287 return self.text.length > 0;
2290- (void)insertText:(NSString*)text {
2291 if (
self.temporarilyDeletedComposedCharacter.length > 0 &&
text.length == 1 && !
text.UTF8String &&
2292 [
text characterAtIndex:0] == [
self.temporarilyDeletedComposedCharacter characterAtIndex:0]) {
2296 text =
self.temporarilyDeletedComposedCharacter;
2297 self.temporarilyDeletedComposedCharacter = nil;
2300 NSMutableArray<FlutterTextSelectionRect*>* copiedRects =
2301 [[NSMutableArray alloc] initWithCapacity:[_selectionRects count]];
2303 @"Expected a FlutterTextPosition for position (got %@).",
2306 for (NSUInteger
i = 0;
i < [_selectionRects count];
i++) {
2307 NSUInteger rectPosition = _selectionRects[i].position;
2308 if (rectPosition == insertPosition) {
2309 for (NSUInteger j = 0; j <=
text.length; j++) {
2316 if (rectPosition > insertPosition) {
2326 _scribbleFocusStatus = FlutterScribbleFocusStatusUnfocused;
2327 [
self resetScribbleInteractionStatusIfEnding];
2328 self.selectionRects = copiedRects;
2330 [
self replaceRange:_selectedTextRange withText:text];
2333- (UITextPlaceholder*)insertTextPlaceholderWithSize:(CGSize)size API_AVAILABLE(ios(13.0)) {
2334 [
self.textInputDelegate flutterTextInputView:self
2335 insertTextPlaceholderWithSize:size
2336 withClient:_textInputClient];
2341- (void)removeTextPlaceholder:(UITextPlaceholder*)textPlaceholder API_AVAILABLE(ios(13.0)) {
2343 [
self.textInputDelegate flutterTextInputView:self removeTextPlaceholder:_textInputClient];
2346- (void)deleteBackward {
2348 _scribbleFocusStatus = FlutterScribbleFocusStatusUnfocused;
2349 [
self resetScribbleInteractionStatusIfEnding];
2366 if (oldRange.location > 0) {
2367 NSRange newRange = NSMakeRange(oldRange.location - 1, 1);
2373 newRange = NSMakeRange(charRange.location, oldRange.location - charRange.location);
2385 NSString* deletedText = [
self.text substringWithRange:_selectedTextRange.range];
2387 self.temporarilyDeletedComposedCharacter =
2388 [deletedText substringWithRange:deleteFirstCharacterRange];
2390 [
self replaceRange:_selectedTextRange withText:@""];
2394- (void)postAccessibilityNotification:(UIAccessibilityNotifications)notification target:(
id)target {
2395 UIAccessibilityPostNotification(notification,
target);
2398- (void)accessibilityElementDidBecomeFocused {
2399 if ([
self accessibilityElementIsFocused]) {
2403 FML_DCHECK(_backingTextInputAccessibilityObject);
2404 [
self postAccessibilityNotification:UIAccessibilityScreenChangedNotification
2405 target:_backingTextInputAccessibilityObject];
2409- (
BOOL)accessibilityElementsHidden {
2410 return !_accessibilityEnabled;
2419#pragma mark - Key Events Handling
2420- (void)pressesBegan:(NSSet<UIPress*>*)presses
2421 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2422 [_textInputPlugin.viewController pressesBegan:presses withEvent:event];
2425- (void)pressesChanged:(NSSet<UIPress*>*)presses
2426 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2427 [_textInputPlugin.viewController pressesChanged:presses withEvent:event];
2430- (void)pressesEnded:(NSSet<UIPress*>*)presses
2431 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2432 [_textInputPlugin.viewController pressesEnded:presses withEvent:event];
2435- (void)pressesCancelled:(NSSet<UIPress*>*)presses
2436 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2437 [_textInputPlugin.viewController pressesCancelled:presses withEvent:event];
2466- (
BOOL)accessibilityElementsHidden {
2473- (void)enableActiveViewAccessibility;
2490- (void)enableActiveViewAccessibility {
2491 [
self.target enableActiveViewAccessibility];
2498@property(nonatomic, readonly)
2499 NSMutableDictionary<NSString*, FlutterTextInputView*>* autofillContext;
2500@property(nonatomic, readonly)
BOOL pendingInputHiderRemoval;
2505@property(nonatomic, strong) UIView* keyboardViewContainer;
2506@property(nonatomic, strong) UIView* keyboardView;
2507@property(nonatomic, strong) UIView* cachedFirstResponder;
2508@property(nonatomic, assign) CGRect keyboardRect;
2509@property(nonatomic, assign) CGFloat previousPointerYPosition;
2510@property(nonatomic, assign) CGFloat pointerYVelocity;
2514 NSTimer* _enableFlutterTextInputViewAccessibilityTimer;
2518- (instancetype)initWithDelegate:(
id<FlutterTextInputDelegate>)textInputDelegate {
2519 self = [
super init];
2522 _textInputDelegate = textInputDelegate;
2523 _autofillContext = [[NSMutableDictionary alloc] init];
2525 _scribbleElements = [[NSMutableDictionary alloc] init];
2526 _keyboardViewContainer = [[UIView alloc] init];
2528 [[NSNotificationCenter defaultCenter] addObserver:self
2529 selector:@selector(handleKeyboardWillShow:)
2530 name:UIKeyboardWillShowNotification
2537- (void)handleKeyboardWillShow:(NSNotification*)notification {
2538 NSDictionary* keyboardInfo = [notification userInfo];
2539 NSValue* keyboardFrameEnd = [keyboardInfo valueForKey:UIKeyboardFrameEndUserInfoKey];
2540 _keyboardRect = [keyboardFrameEnd CGRectValue];
2547- (void)removeEnableFlutterTextInputViewAccessibilityTimer {
2548 if (_enableFlutterTextInputViewAccessibilityTimer) {
2549 [_enableFlutterTextInputViewAccessibilityTimer invalidate];
2550 _enableFlutterTextInputViewAccessibilityTimer = nil;
2554- (UIView<UITextInput>*)textInputView {
2559 [_autofillContext removeAllObjects];
2560 [
self clearTextInputClient];
2561 [
self hideTextInput];
2565 NSString* method = call.
method;
2568 [
self showTextInput];
2570 }
else if ([method isEqualToString:
kHideMethod]) {
2571 [
self hideTextInput];
2574 [
self setTextInputClient:[args[0] intValue] withConfiguration:args[1]];
2578 [
self setPlatformViewTextInputClient];
2581 [
self setTextInputEditingState:args];
2584 [
self clearTextInputClient];
2587 [
self setEditableSizeAndTransform:args];
2590 [
self updateMarkedRect:args];
2593 [
self triggerAutofillSave:[args boolValue]];
2599 [
self setSelectionRects:args];
2602 [
self setSelectionRects:args];
2605 [
self startLiveTextInput];
2608 [
self updateConfig:args];
2611 CGFloat pointerY = (CGFloat)[
args[
@"pointerY"] doubleValue];
2612 [
self handlePointerMove:pointerY];
2615 CGFloat pointerY = (CGFloat)[
args[
@"pointerY"] doubleValue];
2616 [
self handlePointerUp:pointerY];