5#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
6#import "flutter/shell/platform/darwin/ios/framework/Source/UIViewController+FlutterScreenAndSceneIfLoaded.h"
8#import <Foundation/Foundation.h>
11#include "unicode/uchar.h"
13#include "flutter/fml/logging.h"
14#include "flutter/fml/platform/darwin/string_range_sanitization.h"
37#pragma mark - TextInput channel method names.
46 @"TextInput.setEditableSizeAndTransform";
57 @"TextInput.onPointerMoveForInteractiveKeyboard";
59 @"TextInput.onPointerUpForInteractiveKeyboard";
61#pragma mark - TextInputConfiguration Field Names
82#pragma mark - Static Functions
87 BOOL gotCodePoint = [
text getBytes:&codePoint
88 maxLength:
sizeof(codePoint)
90 encoding:NSUTF32StringEncoding
94 return gotCodePoint && u_hasBinaryProperty(codePoint, UCHAR_EMOJI);
102 NSString* inputType =
type[
@"name"];
103 return ![inputType isEqualToString:
@"TextInputType.none"];
106 NSString* inputType =
type[
@"name"];
107 if ([inputType isEqualToString:
@"TextInputType.address"]) {
108 return UIKeyboardTypeDefault;
110 if ([inputType isEqualToString:
@"TextInputType.datetime"]) {
111 return UIKeyboardTypeNumbersAndPunctuation;
113 if ([inputType isEqualToString:
@"TextInputType.emailAddress"]) {
114 return UIKeyboardTypeEmailAddress;
116 if ([inputType isEqualToString:
@"TextInputType.multiline"]) {
117 return UIKeyboardTypeDefault;
119 if ([inputType isEqualToString:
@"TextInputType.name"]) {
120 return UIKeyboardTypeNamePhonePad;
122 if ([inputType isEqualToString:
@"TextInputType.number"]) {
123 if ([
type[
@"signed"] boolValue]) {
124 return UIKeyboardTypeNumbersAndPunctuation;
126 if ([
type[
@"decimal"] boolValue]) {
127 return UIKeyboardTypeDecimalPad;
129 return UIKeyboardTypeNumberPad;
131 if ([inputType isEqualToString:
@"TextInputType.phone"]) {
132 return UIKeyboardTypePhonePad;
134 if ([inputType isEqualToString:
@"TextInputType.text"]) {
135 return UIKeyboardTypeDefault;
137 if ([inputType isEqualToString:
@"TextInputType.url"]) {
138 return UIKeyboardTypeURL;
140 if ([inputType isEqualToString:
@"TextInputType.visiblePassword"]) {
141 return UIKeyboardTypeASCIICapable;
143 return UIKeyboardTypeDefault;
147 NSString* textCapitalization =
type[
@"textCapitalization"];
148 if ([textCapitalization isEqualToString:
@"TextCapitalization.characters"]) {
149 return UITextAutocapitalizationTypeAllCharacters;
150 }
else if ([textCapitalization isEqualToString:
@"TextCapitalization.sentences"]) {
151 return UITextAutocapitalizationTypeSentences;
152 }
else if ([textCapitalization isEqualToString:
@"TextCapitalization.words"]) {
153 return UITextAutocapitalizationTypeWords;
155 return UITextAutocapitalizationTypeNone;
163 if ([inputType isEqualToString:
@"TextInputAction.unspecified"]) {
164 return UIReturnKeyDefault;
167 if ([inputType isEqualToString:
@"TextInputAction.done"]) {
168 return UIReturnKeyDone;
171 if ([inputType isEqualToString:
@"TextInputAction.go"]) {
172 return UIReturnKeyGo;
175 if ([inputType isEqualToString:
@"TextInputAction.send"]) {
176 return UIReturnKeySend;
179 if ([inputType isEqualToString:
@"TextInputAction.search"]) {
180 return UIReturnKeySearch;
183 if ([inputType isEqualToString:
@"TextInputAction.next"]) {
184 return UIReturnKeyNext;
187 if ([inputType isEqualToString:
@"TextInputAction.continueAction"]) {
188 return UIReturnKeyContinue;
191 if ([inputType isEqualToString:
@"TextInputAction.join"]) {
192 return UIReturnKeyJoin;
195 if ([inputType isEqualToString:
@"TextInputAction.route"]) {
196 return UIReturnKeyRoute;
199 if ([inputType isEqualToString:
@"TextInputAction.emergencyCall"]) {
200 return UIReturnKeyEmergencyCall;
203 if ([inputType isEqualToString:
@"TextInputAction.newline"]) {
204 return UIReturnKeyDefault;
208 return UIReturnKeyDefault;
212 if (!hints || hints.count == 0) {
217 NSString* hint = hints[0];
218 if ([hint isEqualToString:
@"addressCityAndState"]) {
219 return UITextContentTypeAddressCityAndState;
222 if ([hint isEqualToString:
@"addressState"]) {
223 return UITextContentTypeAddressState;
226 if ([hint isEqualToString:
@"addressCity"]) {
227 return UITextContentTypeAddressCity;
230 if ([hint isEqualToString:
@"sublocality"]) {
231 return UITextContentTypeSublocality;
234 if ([hint isEqualToString:
@"streetAddressLine1"]) {
235 return UITextContentTypeStreetAddressLine1;
238 if ([hint isEqualToString:
@"streetAddressLine2"]) {
239 return UITextContentTypeStreetAddressLine2;
242 if ([hint isEqualToString:
@"countryName"]) {
243 return UITextContentTypeCountryName;
246 if ([hint isEqualToString:
@"fullStreetAddress"]) {
247 return UITextContentTypeFullStreetAddress;
250 if ([hint isEqualToString:
@"postalCode"]) {
251 return UITextContentTypePostalCode;
254 if ([hint isEqualToString:
@"location"]) {
255 return UITextContentTypeLocation;
258 if ([hint isEqualToString:
@"creditCardNumber"]) {
259 return UITextContentTypeCreditCardNumber;
262 if ([hint isEqualToString:
@"email"]) {
263 return UITextContentTypeEmailAddress;
266 if ([hint isEqualToString:
@"jobTitle"]) {
267 return UITextContentTypeJobTitle;
270 if ([hint isEqualToString:
@"givenName"]) {
271 return UITextContentTypeGivenName;
274 if ([hint isEqualToString:
@"middleName"]) {
275 return UITextContentTypeMiddleName;
278 if ([hint isEqualToString:
@"familyName"]) {
279 return UITextContentTypeFamilyName;
282 if ([hint isEqualToString:
@"name"]) {
283 return UITextContentTypeName;
286 if ([hint isEqualToString:
@"namePrefix"]) {
287 return UITextContentTypeNamePrefix;
290 if ([hint isEqualToString:
@"nameSuffix"]) {
291 return UITextContentTypeNameSuffix;
294 if ([hint isEqualToString:
@"nickname"]) {
295 return UITextContentTypeNickname;
298 if ([hint isEqualToString:
@"organizationName"]) {
299 return UITextContentTypeOrganizationName;
302 if ([hint isEqualToString:
@"telephoneNumber"]) {
303 return UITextContentTypeTelephoneNumber;
306 if ([hint isEqualToString:
@"password"]) {
307 return UITextContentTypePassword;
310 if ([hint isEqualToString:
@"oneTimeCode"]) {
311 return UITextContentTypeOneTimeCode;
314 if ([hint isEqualToString:
@"newPassword"]) {
315 return UITextContentTypeNewPassword;
383typedef NS_ENUM(NSInteger, FlutterAutofillType) {
387 kFlutterAutofillTypeNone,
388 kFlutterAutofillTypeRegular,
389 kFlutterAutofillTypePassword,
399 if (isSecureTextEntry) {
406 if ([contentType isEqualToString:UITextContentTypePassword] ||
407 [contentType isEqualToString:UITextContentTypeUsername]) {
411 if ([contentType isEqualToString:UITextContentTypeNewPassword]) {
421 return kFlutterAutofillTypePassword;
426 return kFlutterAutofillTypePassword;
431 return !autofill || [contentType isEqualToString:
@""] ? kFlutterAutofillTypeNone
432 : kFlutterAutofillTypeRegular;
462 CGRect selectionRect,
463 BOOL selectionRectIsRTL,
464 BOOL useTrailingBoundaryOfSelectionRect,
465 CGRect otherSelectionRect,
466 BOOL otherSelectionRectIsRTL,
467 CGFloat verticalPrecision) {
469 if (CGRectContainsPoint(
471 selectionRect.origin.x + ((useTrailingBoundaryOfSelectionRect ^ selectionRectIsRTL)
472 ? 0.5 * selectionRect.size.width
474 selectionRect.origin.y, 0.5 * selectionRect.size.width, selectionRect.size.height),
479 CGPoint pointForSelectionRect = CGPointMake(
480 selectionRect.origin.x +
481 (selectionRectIsRTL ^ useTrailingBoundaryOfSelectionRect ? selectionRect.size.width : 0),
482 selectionRect.origin.y + selectionRect.size.height * 0.5);
483 float yDist = fabs(pointForSelectionRect.y - point.y);
484 float xDist = fabs(pointForSelectionRect.x - point.x);
487 CGPoint pointForOtherSelectionRect = CGPointMake(
488 otherSelectionRect.origin.x + (otherSelectionRectIsRTL ? otherSelectionRect.size.width : 0),
489 otherSelectionRect.origin.y + otherSelectionRect.size.height * 0.5);
490 float yDistOther = fabs(pointForOtherSelectionRect.y - point.y);
491 float xDistOther = fabs(pointForOtherSelectionRect.x - point.x);
496 BOOL isCloserVertically = yDist < yDistOther - verticalPrecision;
498 BOOL isAboveBottomOfLine = point.y <= selectionRect.origin.y + selectionRect.size.height;
499 BOOL isCloserHorizontally = xDist < xDistOther;
500 BOOL isBelowBottomOfLine = point.y > selectionRect.origin.y + selectionRect.size.height;
503 if (selectionRectIsRTL) {
504 isFarther = selectionRect.origin.x < otherSelectionRect.origin.x;
506 isFarther = selectionRect.origin.x +
507 (useTrailingBoundaryOfSelectionRect ? selectionRect.size.width : 0) >
508 otherSelectionRect.origin.x;
510 return (isCloserVertically ||
511 (isEqualVertically &&
512 ((isAboveBottomOfLine && isCloserHorizontally) || (isBelowBottomOfLine && isFarther))));
515#pragma mark - FlutterTextPosition
519+ (instancetype)positionWithIndex:(NSUInteger)index {
520 return [[
FlutterTextPosition alloc] initWithIndex:index affinity:UITextStorageDirectionForward];
523+ (instancetype)positionWithIndex:(NSUInteger)index affinity:(UITextStorageDirection)affinity {
527- (instancetype)initWithIndex:(NSUInteger)index affinity:(UITextStorageDirection)affinity {
538#pragma mark - FlutterTextRange
542+ (instancetype)rangeWithNSRange:(NSRange)range {
546- (instancetype)initWithNSRange:(NSRange)range {
554- (UITextPosition*)
start {
556 affinity:UITextStorageDirectionForward];
559- (UITextPosition*)
end {
561 affinity:UITextStorageDirectionBackward];
565 return self.range.length == 0;
568- (
id)copyWithZone:(NSZone*)zone {
573 return NSEqualRanges(
self.range, other.
range);
577#pragma mark - FlutterTokenizer
587- (instancetype)initWithTextInput:(UIResponder<UITextInput>*)textInput {
589 @"The FlutterTokenizer can only be used in a FlutterTextInputView");
590 self = [
super initWithTextInput:textInput];
597- (UITextRange*)rangeEnclosingPosition:(UITextPosition*)position
598 withGranularity:(UITextGranularity)granularity
599 inDirection:(UITextDirection)direction {
601 switch (granularity) {
602 case UITextGranularityLine:
605 result = [
self lineEnclosingPosition:position inDirection:direction];
607 case UITextGranularityCharacter:
608 case UITextGranularityWord:
609 case UITextGranularitySentence:
610 case UITextGranularityParagraph:
611 case UITextGranularityDocument:
613 result = [
super rangeEnclosingPosition:position
614 withGranularity:granularity
615 inDirection:direction];
621- (UITextRange*)lineEnclosingPosition:(UITextPosition*)position
622 inDirection:(UITextDirection)direction {
624 if (@available(iOS 17.0, *)) {
629 if (flutterPosition.
index > _textInputView.text.length ||
630 (flutterPosition.
index == _textInputView.text.length &&
631 direction == UITextStorageDirectionForward)) {
637 NSString* textAfter = [_textInputView
638 textInRange:[_textInputView textRangeFromPosition:position
639 toPosition:[_textInputView endOfDocument]]];
640 NSArray<NSString*>* linesAfter = [textAfter componentsSeparatedByString:@"\n"];
641 NSInteger offSetToLineBreak = [linesAfter firstObject].length;
642 UITextPosition* lineBreakAfter = [_textInputView positionFromPosition:position
643 offset:offSetToLineBreak];
645 NSString* textBefore = [_textInputView
646 textInRange:[_textInputView textRangeFromPosition:[_textInputView beginningOfDocument]
647 toPosition:position]];
648 NSArray<NSString*>* linesBefore = [textBefore componentsSeparatedByString:@"\n"];
649 NSInteger offSetFromLineBreak = [linesBefore lastObject].length;
650 UITextPosition* lineBreakBefore = [_textInputView positionFromPosition:position
651 offset:-offSetFromLineBreak];
653 return [_textInputView textRangeFromPosition:lineBreakBefore toPosition:lineBreakAfter];
658#pragma mark - FlutterTextSelectionRect
662@synthesize rect = _rect;
668+ (instancetype)selectionRectWithRectAndInfo:(CGRect)rect
669 position:(NSUInteger)position
670 writingDirection:(NSWritingDirection)writingDirection
671 containsStart:(
BOOL)containsStart
672 containsEnd:(
BOOL)containsEnd
673 isVertical:(
BOOL)isVertical {
676 writingDirection:writingDirection
677 containsStart:containsStart
678 containsEnd:containsEnd
679 isVertical:isVertical];
682+ (instancetype)selectionRectWithRect:(CGRect)rect position:(NSUInteger)position {
685 writingDirection:NSWritingDirectionNatural
691+ (instancetype)selectionRectWithRect:(CGRect)rect
692 position:(NSUInteger)position
693 writingDirection:(NSWritingDirection)writingDirection {
696 writingDirection:writingDirection
702- (instancetype)initWithRectAndInfo:(CGRect)rect
703 position:(NSUInteger)position
704 writingDirection:(NSWritingDirection)writingDirection
705 containsStart:(
BOOL)containsStart
706 containsEnd:(
BOOL)containsEnd
707 isVertical:(
BOOL)isVertical {
721 return _writingDirection == NSWritingDirectionRightToLeft;
726#pragma mark - FlutterTextPlaceholder
730- (NSArray<UITextSelectionRect*>*)
rects {
746@property(nonatomic, retain, readonly) UITextField*
textField;
760- (
BOOL)isKindOfClass:(Class)aClass {
761 return [
super isKindOfClass:aClass] || (aClass == [UITextField class]);
764- (NSMethodSignature*)methodSignatureForSelector:(
SEL)aSelector {
765 NSMethodSignature* signature = [
super methodSignatureForSelector:aSelector];
772- (void)forwardInvocation:(NSInvocation*)anInvocation {
773 [anInvocation invokeWithTarget:self.textField];
780@property(nonatomic, readonly) UIView*
hostView;
799- (void)setEditableTransform:(NSArray*)matrix;
803 int _textInputClient;
823 self = [
super initWithFrame:CGRectZero];
826 _textInputClient = 0;
828 _preventCursorDismissWhenResignFirstResponder = NO;
831 _text = [[NSMutableString alloc] init];
836 _pendingDeltas = [[NSMutableArray alloc] init];
842 _autocapitalizationType = UITextAutocapitalizationTypeSentences;
843 _autocorrectionType = UITextAutocorrectionTypeDefault;
844 _spellCheckingType = UITextSpellCheckingTypeDefault;
845 _enablesReturnKeyAutomatically = NO;
846 _keyboardAppearance = UIKeyboardAppearanceDefault;
847 _keyboardType = UIKeyboardTypeDefault;
848 _returnKeyType = UIReturnKeyDone;
849 _secureTextEntry = NO;
850 _enableDeltaModel = NO;
852 _accessibilityEnabled = NO;
853 _smartQuotesType = UITextSmartQuotesTypeYes;
854 _smartDashesType = UITextSmartDashesTypeYes;
855 _selectionRects = [[NSArray alloc] init];
857 if (@available(iOS 14.0, *)) {
858 UIScribbleInteraction* interaction = [[UIScribbleInteraction alloc] initWithDelegate:self];
859 [
self addInteraction:interaction];
863 if (@available(iOS 16.0, *)) {
864 _editMenuInteraction = [[UIEditMenuInteraction alloc] initWithDelegate:self];
865 [
self addInteraction:_editMenuInteraction];
871- (UIMenu*)editMenuInteraction:(UIEditMenuInteraction*)interaction
872 menuForConfiguration:(UIEditMenuConfiguration*)configuration
873 suggestedActions:(NSArray<UIMenuElement*>*)suggestedActions API_AVAILABLE(
ios(16.0)) {
874 return [UIMenu menuWithChildren:suggestedActions];
877- (void)editMenuInteraction:(UIEditMenuInteraction*)interaction
878 willDismissMenuForConfiguration:(UIEditMenuConfiguration*)configuration
879 animator:(
id<UIEditMenuInteractionAnimating>)animator
880 API_AVAILABLE(
ios(16.0)) {
881 [
self.textInputDelegate flutterTextInputView:self
882 willDismissEditMenuWithTextInputClient:_textInputClient];
885- (CGRect)editMenuInteraction:(UIEditMenuInteraction*)interaction
886 targetRectForConfiguration:(UIEditMenuConfiguration*)configuration API_AVAILABLE(
ios(16.0)) {
887 return _editMenuTargetRect;
890- (void)showEditMenuWithTargetRect:(CGRect)targetRect API_AVAILABLE(
ios(16.0)) {
891 _editMenuTargetRect = targetRect;
892 UIEditMenuConfiguration* config =
893 [UIEditMenuConfiguration configurationWithIdentifier:nil sourcePoint:CGPointZero];
894 [
self.editMenuInteraction presentEditMenuWithConfiguration:config];
898 [
self.editMenuInteraction dismissMenu];
901- (void)configureWithDictionary:(NSDictionary*)configuration {
902 NSDictionary* inputType = configuration[kKeyboardType];
904 NSDictionary* autofill = configuration[kAutofillProperties];
906 self.secureTextEntry = [configuration[kSecureTextEntry] boolValue];
907 self.enableDeltaModel = [configuration[kEnableDeltaModel] boolValue];
914 NSString* smartDashesType = configuration[kSmartDashesType];
916 bool smartDashesIsDisabled = smartDashesType && [smartDashesType isEqualToString:@"0"];
917 self.smartDashesType = smartDashesIsDisabled ? UITextSmartDashesTypeNo : UITextSmartDashesTypeYes;
918 NSString* smartQuotesType = configuration[kSmartQuotesType];
920 bool smartQuotesIsDisabled = smartQuotesType && [smartQuotesType isEqualToString:@"0"];
921 self.smartQuotesType = smartQuotesIsDisabled ? UITextSmartQuotesTypeNo : UITextSmartQuotesTypeYes;
923 self.keyboardAppearance = UIKeyboardAppearanceDark;
925 self.keyboardAppearance = UIKeyboardAppearanceLight;
927 self.keyboardAppearance = UIKeyboardAppearanceDefault;
929 NSString* autocorrect = configuration[kAutocorrectionType];
930 bool autocorrectIsDisabled = autocorrect && ![autocorrect boolValue];
931 self.autocorrectionType =
932 autocorrectIsDisabled ? UITextAutocorrectionTypeNo : UITextAutocorrectionTypeDefault;
933 self.spellCheckingType =
934 autocorrectIsDisabled ? UITextSpellCheckingTypeNo : UITextSpellCheckingTypeDefault;
936 if (autofill == nil) {
937 self.textContentType =
@"";
940 [
self setTextInputState:autofill[kAutofillEditingValue]];
941 NSAssert(_autofillId,
@"The autofill configuration must contain an autofill id");
945 self.isVisibleToAutofill = autofill || _secureTextEntry;
948- (UITextContentType)textContentType {
949 return _textContentType;
962- (UIColor*)insertionPointColor {
963 return [UIColor clearColor];
966- (UIColor*)selectionBarColor {
967 return [UIColor clearColor];
970- (UIColor*)selectionHighlightColor {
971 return [UIColor clearColor];
974- (UIInputViewController*)inputViewController {
989- (
BOOL)respondsToSelector:(
SEL)selector {
990 if (@available(iOS 17.0, *)) {
992 if (selector ==
@selector(insertionPointColor)) {
996 return [
super respondsToSelector:selector];
999- (void)setTextInputClient:(
int)client {
1000 _textInputClient = client;
1005 if (!_textInteraction) {
1006 _textInteraction = [UITextInteraction textInteractionForMode:UITextInteractionModeEditable];
1007 _textInteraction.textInput =
self;
1009 return _textInteraction;
1012- (void)setTextInputState:(NSDictionary*)state {
1013 if (@available(iOS 13.0, *)) {
1019 if (!
self.inputDelegate &&
self.isFirstResponder) {
1020 [
self addInteraction:self.textInteraction];
1024 NSString* newText =
state[@"text"];
1025 BOOL textChanged = ![
self.text isEqualToString:newText];
1027 [
self.inputDelegate textWillChange:self];
1028 [
self.text setString:newText];
1030 NSInteger composingBase = [state[@"composingBase"] intValue];
1031 NSInteger composingExtent = [state[@"composingExtent"] intValue];
1032 NSRange composingRange = [
self clampSelection:NSMakeRange(MIN(composingBase, composingExtent),
1033 ABS(composingBase - composingExtent))
1036 self.markedTextRange =
1039 NSRange selectedRange = [
self clampSelectionFromBase:[state[@"selectionBase"] intValue]
1040 extent:[state[@"selectionExtent"] intValue]
1043 NSRange oldSelectedRange = [(
FlutterTextRange*)
self.selectedTextRange range];
1044 if (!NSEqualRanges(selectedRange, oldSelectedRange)) {
1045 [
self.inputDelegate selectionWillChange:self];
1053 [
self.inputDelegate selectionDidChange:self];
1057 [
self.inputDelegate textDidChange:self];
1060 if (@available(iOS 13.0, *)) {
1061 if (_textInteraction) {
1062 [
self removeInteraction:_textInteraction];
1068- (void)touchesBegan:(NSSet*)touches withEvent:(
UIEvent*)event {
1069 _scribbleFocusStatus = FlutterScribbleFocusStatusUnfocused;
1070 [
self resetScribbleInteractionStatusIfEnding];
1071 [
self.viewResponder touchesBegan:touches withEvent:event];
1074- (void)touchesMoved:(NSSet*)touches withEvent:(
UIEvent*)event {
1075 [
self.viewResponder touchesMoved:touches withEvent:event];
1078- (void)touchesEnded:(NSSet*)touches withEvent:(
UIEvent*)event {
1079 [
self.viewResponder touchesEnded:touches withEvent:event];
1082- (void)touchesCancelled:(NSSet*)touches withEvent:(
UIEvent*)event {
1083 [
self.viewResponder touchesCancelled:touches withEvent:event];
1086- (void)touchesEstimatedPropertiesUpdated:(NSSet*)touches {
1087 [
self.viewResponder touchesEstimatedPropertiesUpdated:touches];
1097- (NSRange)clampSelectionFromBase:(
int)selectionBase
1098 extent:(
int)selectionExtent
1099 forText:(NSString*)text {
1100 int loc =
MIN(selectionBase, selectionExtent);
1101 int len = ABS(selectionExtent - selectionBase);
1102 return loc < 0 ? NSMakeRange(0, 0)
1106- (NSRange)clampSelection:(NSRange)range forText:(NSString*)text {
1112- (
BOOL)isVisibleToAutofill {
1113 return self.frame.size.width > 0 &&
self.frame.size.height > 0;
1121- (void)setIsVisibleToAutofill:(
BOOL)isVisibleToAutofill {
1124 self.frame = isVisibleToAutofill ? CGRectMake(0, 0, 1, 1) : CGRectZero;
1127#pragma mark UIScribbleInteractionDelegate
1132 if (@available(iOS 14.0, *)) {
1133 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
1140- (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction*)interaction
1141 API_AVAILABLE(
ios(14.0)) {
1143 [
self.textInputDelegate flutterTextInputViewScribbleInteractionBegan:self];
1146- (void)scribbleInteractionDidFinishWriting:(UIScribbleInteraction*)interaction
1147 API_AVAILABLE(
ios(14.0)) {
1149 [
self.textInputDelegate flutterTextInputViewScribbleInteractionFinished:self];
1152- (
BOOL)scribbleInteraction:(UIScribbleInteraction*)interaction
1153 shouldBeginAtLocation:(CGPoint)location API_AVAILABLE(
ios(14.0)) {
1157- (
BOOL)scribbleInteractionShouldDelayFocus:(UIScribbleInteraction*)interaction
1158 API_AVAILABLE(
ios(14.0)) {
1162#pragma mark - UIResponder Overrides
1164- (
BOOL)canBecomeFirstResponder {
1169 return _textInputClient != 0;
1172- (
BOOL)resignFirstResponder {
1173 BOOL success = [
super resignFirstResponder];
1175 if (!_preventCursorDismissWhenResignFirstResponder) {
1176 [
self.textInputDelegate flutterTextInputView:self
1177 didResignFirstResponderWithTextInputClient:_textInputClient];
1183- (
BOOL)canPerformAction:(
SEL)action withSender:(
id)sender {
1184 if (
action ==
@selector(paste:)) {
1186 return [UIPasteboard generalPasteboard].hasStrings;
1188 action ==
@selector(
delete:)) {
1189 return [
self textInRange:_selectedTextRange].length > 0;
1191 return [
super canPerformAction:action withSender:sender];
1194#pragma mark - UIResponderStandardEditActions Overrides
1196- (void)cut:(
id)sender {
1197 [UIPasteboard generalPasteboard].string = [
self textInRange:_selectedTextRange];
1198 [
self replaceRange:_selectedTextRange withText:@""];
1201- (void)
copy:(
id)sender {
1202 [UIPasteboard generalPasteboard].string = [
self textInRange:_selectedTextRange];
1205- (void)paste:(
id)sender {
1206 NSString* pasteboardString = [UIPasteboard generalPasteboard].string;
1207 if (pasteboardString != nil) {
1208 [
self insertText:pasteboardString];
1212- (void)
delete:(
id)sender {
1213 [
self replaceRange:_selectedTextRange withText:@""];
1216- (void)selectAll:(
id)sender {
1217 [
self setSelectedTextRange:[
self textRangeFromPosition:[
self beginningOfDocument]
1218 toPosition:[
self endOfDocument]]];
1221#pragma mark - UITextInput Overrides
1223- (
id<UITextInputTokenizer>)tokenizer {
1224 if (_tokenizer == nil) {
1231 return [_selectedTextRange copy];
1235- (void)setSelectedTextRangeLocal:(UITextRange*)selectedTextRange {
1240 rangeWithNSRange:fml::RangeForCharactersInRange(self.text, flutterTextRange.range)] copy];
1247- (void)setSelectedTextRange:(UITextRange*)selectedTextRange {
1252 [
self setSelectedTextRangeLocal:selectedTextRange];
1254 if (_enableDeltaModel) {
1255 [
self updateEditingStateWithDelta:flutter::TextEditingDelta([
self.text UTF8String])];
1257 [
self updateEditingState];
1261 _scribbleFocusStatus == FlutterScribbleFocusStatusFocused) {
1265 if (flutterTextRange.
range.length > 0) {
1266 [
self.textInputDelegate flutterTextInputView:self showToolbar:_textInputClient];
1270 [
self resetScribbleInteractionStatusIfEnding];
1273- (
id)insertDictationResultPlaceholder {
1277- (void)removeDictationResultPlaceholder:(
id)placeholder willInsertResult:(
BOOL)willInsertResult {
1280- (NSString*)textInRange:(UITextRange*)range {
1285 @"Expected a FlutterTextRange for range (got %@).", [range
class]);
1287 NSAssert(textRange.location != NSNotFound,
@"Expected a valid text range.");
1289 NSUInteger location =
MIN(textRange.location,
self.text.length);
1290 NSUInteger
length =
MIN(
self.text.length - location, textRange.length);
1291 NSRange safeRange = NSMakeRange(location,
length);
1292 return [
self.text substringWithRange:safeRange];
1297- (void)replaceRangeLocal:(NSRange)range withText:(NSString*)text {
1298 [
self.text replaceCharactersInRange:[
self clampSelection:range forText:self.text]
1304 const NSRange newSelectionRange =
1305 [
self clampSelection:NSMakeRange(range.location + text.length, 0) forText:self.text];
1308 self.markedTextRange = nil;
1311- (void)replaceRange:(UITextRange*)range withText:(NSString*)text {
1312 NSString* textBeforeChange = [
self.text copy];
1314 [
self replaceRangeLocal:replaceRange withText:text];
1315 if (_enableDeltaModel) {
1316 NSRange nextReplaceRange = [
self clampSelection:replaceRange forText:textBeforeChange];
1317 [
self updateEditingStateWithDelta:flutter::TextEditingDelta(
1318 [textBeforeChange UTF8String],
1320 nextReplaceRange.location,
1321 nextReplaceRange.location + nextReplaceRange.length),
1322 [text UTF8String])];
1324 [
self updateEditingState];
1328- (
BOOL)shouldChangeTextInRange:(UITextRange*)range replacementText:(NSString*)text {
1331 self.temporarilyDeletedComposedCharacter = nil;
1333 if (
self.returnKeyType == UIReturnKeyDefault && [
text isEqualToString:
@"\n"]) {
1334 [
self.textInputDelegate flutterTextInputView:self
1335 performAction:FlutterTextInputActionNewline
1336 withClient:_textInputClient];
1340 if ([
text isEqualToString:
@"\n"]) {
1341 FlutterTextInputAction
action;
1342 switch (
self.returnKeyType) {
1343 case UIReturnKeyDefault:
1344 action = FlutterTextInputActionUnspecified;
1346 case UIReturnKeyDone:
1347 action = FlutterTextInputActionDone;
1350 action = FlutterTextInputActionGo;
1352 case UIReturnKeySend:
1353 action = FlutterTextInputActionSend;
1355 case UIReturnKeySearch:
1356 case UIReturnKeyGoogle:
1357 case UIReturnKeyYahoo:
1358 action = FlutterTextInputActionSearch;
1360 case UIReturnKeyNext:
1361 action = FlutterTextInputActionNext;
1363 case UIReturnKeyContinue:
1364 action = FlutterTextInputActionContinue;
1366 case UIReturnKeyJoin:
1367 action = FlutterTextInputActionJoin;
1369 case UIReturnKeyRoute:
1370 action = FlutterTextInputActionRoute;
1372 case UIReturnKeyEmergencyCall:
1373 action = FlutterTextInputActionEmergencyCall;
1377 [
self.textInputDelegate flutterTextInputView:self
1378 performAction:action
1379 withClient:_textInputClient];
1388- (void)setMarkedText:(NSString*)markedText selectedRange:(NSRange)markedSelectedRange {
1389 NSString* textBeforeChange = [
self.text copy];
1392 _scribbleFocusStatus != FlutterScribbleFocusStatusUnfocused) {
1396 if (markedText == nil) {
1401 const NSRange& actualReplacedRange = currentMarkedTextRange && !currentMarkedTextRange.
isEmpty
1402 ? currentMarkedTextRange.
range
1406 [
self.text replaceCharactersInRange:actualReplacedRange withString:markedText];
1408 const NSRange newMarkedRange = NSMakeRange(actualReplacedRange.location, markedText.length);
1409 self.markedTextRange =
1412 [
self setSelectedTextRangeLocal:
1414 rangeWithNSRange:[
self clampSelection:NSMakeRange(markedSelectedRange.location +
1415 newMarkedRange.location,
1416 markedSelectedRange.length)
1417 forText:self.text]]];
1418 if (_enableDeltaModel) {
1419 NSRange nextReplaceRange = [
self clampSelection:actualReplacedRange forText:textBeforeChange];
1420 [
self updateEditingStateWithDelta:flutter::TextEditingDelta(
1421 [textBeforeChange UTF8String],
1423 nextReplaceRange.location,
1424 nextReplaceRange.location + nextReplaceRange.length),
1425 [markedText UTF8String])];
1427 [
self updateEditingState];
1432 if (!
self.markedTextRange) {
1435 self.markedTextRange = nil;
1436 if (_enableDeltaModel) {
1437 [
self updateEditingStateWithDelta:flutter::TextEditingDelta([
self.text UTF8String])];
1439 [
self updateEditingState];
1443- (UITextRange*)textRangeFromPosition:(UITextPosition*)fromPosition
1444 toPosition:(UITextPosition*)toPosition {
1447 if (toIndex >= fromIndex) {
1460- (NSUInteger)decrementOffsetPosition:(NSUInteger)position {
1464- (NSUInteger)incrementOffsetPosition:(NSUInteger)position {
1466 return MIN(position + charRange.length,
self.text.length);
1469- (UITextPosition*)positionFromPosition:(UITextPosition*)position offset:(NSInteger)offset {
1472 NSInteger newLocation = (NSInteger)offsetPosition +
offset;
1473 if (newLocation < 0 || newLocation > (NSInteger)
self.text.length) {
1482 for (NSInteger
i = 0;
i <
offset && offsetPosition <
self.text.length; ++
i) {
1483 offsetPosition = [
self incrementOffsetPosition:offsetPosition];
1486 for (NSInteger
i = 0;
i < ABS(
offset) && offsetPosition > 0; ++
i) {
1487 offsetPosition = [
self decrementOffsetPosition:offsetPosition];
1493- (UITextPosition*)positionFromPosition:(UITextPosition*)position
1494 inDirection:(UITextLayoutDirection)direction
1495 offset:(NSInteger)offset {
1497 switch (direction) {
1498 case UITextLayoutDirectionLeft:
1499 case UITextLayoutDirectionUp:
1500 return [
self positionFromPosition:position offset:offset * -1];
1501 case UITextLayoutDirectionRight:
1502 case UITextLayoutDirectionDown:
1503 return [
self positionFromPosition:position offset:1];
1507- (UITextPosition*)beginningOfDocument {
1511- (UITextPosition*)endOfDocument {
1513 affinity:UITextStorageDirectionBackward];
1516- (NSComparisonResult)comparePosition:(UITextPosition*)position toPosition:(UITextPosition*)other {
1519 if (positionIndex < otherIndex) {
1520 return NSOrderedAscending;
1522 if (positionIndex > otherIndex) {
1523 return NSOrderedDescending;
1527 if (positionAffinity == otherAffinity) {
1528 return NSOrderedSame;
1530 if (positionAffinity == UITextStorageDirectionBackward) {
1532 return NSOrderedAscending;
1535 return NSOrderedDescending;
1538- (NSInteger)offsetFromPosition:(UITextPosition*)from toPosition:(UITextPosition*)toPosition {
1542- (UITextPosition*)positionWithinRange:(UITextRange*)range
1543 farthestInDirection:(UITextLayoutDirection)direction {
1545 UITextStorageDirection affinity;
1546 switch (direction) {
1547 case UITextLayoutDirectionLeft:
1548 case UITextLayoutDirectionUp:
1550 affinity = UITextStorageDirectionForward;
1552 case UITextLayoutDirectionRight:
1553 case UITextLayoutDirectionDown:
1555 affinity = UITextStorageDirectionBackward;
1561- (UITextRange*)characterRangeByExtendingPosition:(UITextPosition*)position
1562 inDirection:(UITextLayoutDirection)direction {
1564 NSUInteger startIndex;
1565 NSUInteger endIndex;
1566 switch (direction) {
1567 case UITextLayoutDirectionLeft:
1568 case UITextLayoutDirectionUp:
1569 startIndex = [
self decrementOffsetPosition:positionIndex];
1570 endIndex = positionIndex;
1572 case UITextLayoutDirectionRight:
1573 case UITextLayoutDirectionDown:
1574 startIndex = positionIndex;
1575 endIndex = [
self incrementOffsetPosition:positionIndex];
1581#pragma mark - UITextInput text direction handling
1583- (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition*)position
1584 inDirection:(UITextStorageDirection)direction {
1586 return UITextWritingDirectionNatural;
1589- (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection
1590 forRange:(UITextRange*)range {
1594#pragma mark - UITextInput cursor, selection rect handling
1596- (void)setMarkedRect:(CGRect)markedRect {
1597 _markedRect = markedRect;
1604- (void)setEditableTransform:(NSArray*)matrix {
1607 transform->m11 = [matrix[0] doubleValue];
1608 transform->m12 = [matrix[1] doubleValue];
1609 transform->m13 = [matrix[2] doubleValue];
1610 transform->m14 = [matrix[3] doubleValue];
1612 transform->m21 = [matrix[4] doubleValue];
1613 transform->m22 = [matrix[5] doubleValue];
1614 transform->m23 = [matrix[6] doubleValue];
1615 transform->m24 = [matrix[7] doubleValue];
1617 transform->m31 = [matrix[8] doubleValue];
1618 transform->m32 = [matrix[9] doubleValue];
1619 transform->m33 = [matrix[10] doubleValue];
1620 transform->m34 = [matrix[11] doubleValue];
1622 transform->m41 = [matrix[12] doubleValue];
1623 transform->m42 = [matrix[13] doubleValue];
1624 transform->m43 = [matrix[14] doubleValue];
1625 transform->m44 = [matrix[15] doubleValue];
1635 incomingRect.origin,
1636 CGPointMake(incomingRect.origin.x, incomingRect.origin.y + incomingRect.size.height),
1637 CGPointMake(incomingRect.origin.x + incomingRect.size.width, incomingRect.origin.y),
1638 CGPointMake(incomingRect.origin.x + incomingRect.size.width,
1639 incomingRect.origin.y + incomingRect.size.height)};
1641 CGPoint origin = CGPointMake(CGFLOAT_MAX, CGFLOAT_MAX);
1642 CGPoint farthest = CGPointMake(-CGFLOAT_MAX, -CGFLOAT_MAX);
1644 for (
int i = 0;
i < 4;
i++) {
1645 const CGPoint point =
points[i];
1657 }
else if (
w != 1.0) {
1662 origin.x =
MIN(origin.x,
x);
1663 origin.y =
MIN(origin.y,
y);
1664 farthest.x =
MAX(farthest.x,
x);
1665 farthest.y =
MAX(farthest.y,
y);
1667 return CGRectMake(origin.x, origin.y, farthest.x - origin.x, farthest.y - origin.y);
1676- (CGRect)firstRectForRange:(UITextRange*)range {
1678 @"Expected a FlutterTextPosition for range.start (got %@).", [range.start
class]);
1680 @"Expected a FlutterTextPosition for range.end (got %@).", [range.end
class]);
1683 if (_markedTextRange != nil) {
1694 CGRect
rect = _markedRect;
1695 if (CGRectIsEmpty(
rect)) {
1702 NSAssert(hostView == nil || [
self isDescendantOfView:hostView],
@"%@ is not a descendant of %@",
1704 return hostView ? [hostView convertRect:_cachedFirstRect toView:self] :
_cachedFirstRect;
1708 _scribbleFocusStatus == FlutterScribbleFocusStatusUnfocused) {
1709 if (@available(iOS 17.0, *)) {
1719 [
self.textInputDelegate flutterTextInputView:self
1720 showAutocorrectionPromptRectForStart:start
1722 withClient:_textInputClient];
1730 if (@available(iOS 17, *)) {
1736 NSUInteger first =
start;
1741 CGRect startSelectionRect = CGRectNull;
1742 CGRect endSelectionRect = CGRectNull;
1745 CGFloat minY = CGFLOAT_MAX;
1746 CGFloat maxY = CGFLOAT_MIN;
1749 rangeWithNSRange:fml::RangeForCharactersInRange(self.text, NSMakeRange(0, self.text.length))];
1750 for (NSUInteger
i = 0;
i < [_selectionRects count];
i++) {
1751 BOOL startsOnOrBeforeStartOfRange = _selectionRects[i].position <= first;
1752 BOOL isLastSelectionRect =
i + 1 == [_selectionRects
count];
1753 BOOL endOfTextIsAfterStartOfRange = isLastSelectionRect && textRange.
range.length > first;
1754 BOOL nextSelectionRectIsAfterStartOfRange =
1755 !isLastSelectionRect && _selectionRects[i + 1].position > first;
1756 if (startsOnOrBeforeStartOfRange &&
1757 (endOfTextIsAfterStartOfRange || nextSelectionRectIsAfterStartOfRange)) {
1759 if (@available(iOS 17, *)) {
1760 startSelectionRect = _selectionRects[i].rect;
1762 return _selectionRects[i].rect;
1765 if (!CGRectIsNull(startSelectionRect)) {
1766 minY = fmin(minY, CGRectGetMinY(_selectionRects[
i].
rect));
1767 maxY = fmax(maxY, CGRectGetMaxY(_selectionRects[
i].
rect));
1768 BOOL endsOnOrAfterEndOfRange = _selectionRects[i].position >=
end - 1;
1769 BOOL nextSelectionRectIsOnNextLine =
1770 !isLastSelectionRect &&
1775 CGRectGetMidY(_selectionRects[
i + 1].
rect) > CGRectGetMaxY(_selectionRects[
i].
rect);
1776 if (endsOnOrAfterEndOfRange || isLastSelectionRect || nextSelectionRectIsOnNextLine) {
1777 endSelectionRect = _selectionRects[i].rect;
1782 if (CGRectIsNull(startSelectionRect) || CGRectIsNull(endSelectionRect)) {
1786 CGFloat minX = fmin(CGRectGetMinX(startSelectionRect), CGRectGetMinX(endSelectionRect));
1787 CGFloat maxX = fmax(CGRectGetMaxX(startSelectionRect), CGRectGetMaxX(endSelectionRect));
1788 return CGRectMake(minX, minY, maxX - minX, maxY - minY);
1796 NSArray<UITextSelectionRect*>* rects = [
self
1798 rangeWithNSRange:fml::RangeForCharactersInRange(
1802 (index >= (NSInteger)self.text.length)
1805 if (rects.count == 0) {
1811 CGRect characterAfterCaret = rects[0].rect;
1816 return CGRectMake(characterAfterCaret.origin.x + characterAfterCaret.size.width,
1817 characterAfterCaret.origin.y, 0, characterAfterCaret.size.height);
1819 return CGRectMake(characterAfterCaret.origin.x, characterAfterCaret.origin.y, 0,
1820 characterAfterCaret.size.height);
1822 }
else if (rects.count == 2 && affinity == UITextStorageDirectionForward) {
1825 CGRect characterAfterCaret = rects[1].rect;
1830 return CGRectMake(characterAfterCaret.origin.x + characterAfterCaret.size.width,
1831 characterAfterCaret.origin.y, 0, characterAfterCaret.size.height);
1833 return CGRectMake(characterAfterCaret.origin.x, characterAfterCaret.origin.y, 0,
1834 characterAfterCaret.size.height);
1843 CGRect characterBeforeCaret = rects[0].rect;
1846 return CGRectMake(characterBeforeCaret.origin.x, characterBeforeCaret.origin.y, 0,
1847 characterBeforeCaret.size.height);
1849 return CGRectMake(characterBeforeCaret.origin.x + characterBeforeCaret.size.width,
1850 characterBeforeCaret.origin.y, 0, characterBeforeCaret.size.height);
1854- (UITextPosition*)closestPositionToPoint:(CGPoint)point {
1855 if ([_selectionRects
count] == 0) {
1857 @"Expected a FlutterTextPosition for position (got %@).",
1860 UITextStorageDirection currentAffinity =
1866 rangeWithNSRange:fml::RangeForCharactersInRange(self.text, NSMakeRange(0, self.text.length))];
1867 return [
self closestPositionToPoint:point withinRange:range];
1870- (NSArray*)selectionRectsForRange:(UITextRange*)range {
1878 @"Expected a FlutterTextPosition for range.start (got %@).", [range.start
class]);
1880 @"Expected a FlutterTextPosition for range.end (got %@).", [range.end
class]);
1883 NSMutableArray* rects = [[NSMutableArray alloc] init];
1884 for (NSUInteger
i = 0;
i < [_selectionRects count];
i++) {
1885 if (_selectionRects[
i].position >=
start &&
1886 (_selectionRects[
i].position <
end ||
1887 (
start ==
end && _selectionRects[
i].position <=
end))) {
1888 float width = _selectionRects[i].rect.size.width;
1892 CGRect
rect = CGRectMake(_selectionRects[
i].
rect.origin.x, _selectionRects[
i].rect.origin.y,
1893 width, _selectionRects[
i].rect.size.height);
1896 position:_selectionRects[i].position
1900 self.text, NSMakeRange(0, self.text.length))
1903 [rects addObject:selectionRect];
1909- (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange*)range {
1911 @"Expected a FlutterTextPosition for range.start (got %@).", [range.start
class]);
1913 @"Expected a FlutterTextPosition for range.end (got %@).", [range.end
class]);
1923 NSUInteger _closestRectIndex = 0;
1924 for (NSUInteger
i = 0;
i < [_selectionRects count];
i++) {
1925 NSUInteger position = _selectionRects[i].position;
1926 if (position >=
start && position <=
end) {
1929 point, _selectionRects[
i].
rect, _selectionRects[
i].isRTL,
1930 NO, _selectionRects[_closestRectIndex].
rect,
1931 _selectionRects[_closestRectIndex].isRTL, verticalPrecision)) {
1933 _closestRectIndex =
i;
1940 affinity:UITextStorageDirectionForward];
1946 for (NSUInteger
i =
MAX(0, _closestRectIndex - 1);
1947 i <
MIN(_closestRectIndex + 2, [_selectionRects
count]);
i++) {
1948 NSUInteger position = _selectionRects[i].position + 1;
1949 if (position >=
start && position <=
end) {
1951 point, _selectionRects[
i].
rect, _selectionRects[
i].isRTL,
1952 YES, _selectionRects[_closestRectIndex].
rect,
1953 _selectionRects[_closestRectIndex].isRTL, verticalPrecision)) {
1956 affinity:UITextStorageDirectionBackward];
1961 return closestPosition;
1964- (UITextRange*)characterRangeAtPoint:(CGPoint)point {
1967 return [
FlutterTextRange rangeWithNSRange:fml::RangeForCharacterAtIndex(self.text, currentIndex)];
1998- (void)beginFloatingCursorAtPoint:(CGPoint)point {
2015 [
self.textInputDelegate flutterTextInputView:self
2016 updateFloatingCursor:FlutterFloatingCursorDragStateStart
2017 withClient:_textInputClient
2018 withPosition:@{@"X" : @0, @"Y" : @0}];
2021- (void)updateFloatingCursorAtPoint:(CGPoint)point {
2022 [
self.textInputDelegate flutterTextInputView:self
2023 updateFloatingCursor:FlutterFloatingCursorDragStateUpdate
2024 withClient:_textInputClient
2026 @"X" : @(point.x - _floatingCursorOffset.x),
2027 @"Y" : @(point.y - _floatingCursorOffset.y)
2031- (void)endFloatingCursor {
2033 [
self.textInputDelegate flutterTextInputView:self
2034 updateFloatingCursor:FlutterFloatingCursorDragStateEnd
2035 withClient:_textInputClient
2036 withPosition:@{@"X" : @0, @"Y" : @0}];
2039#pragma mark - UIKeyInput Overrides
2041- (void)updateEditingState {
2046 NSInteger composingBase = -1;
2047 NSInteger composingExtent = -1;
2048 if (
self.markedTextRange != nil) {
2052 NSDictionary*
state = @{
2053 @"selectionBase" : @(selectionBase),
2054 @"selectionExtent" : @(selectionExtent),
2056 @"selectionIsDirectional" : @(
false),
2057 @"composingBase" : @(composingBase),
2058 @"composingExtent" : @(composingExtent),
2059 @"text" : [NSString stringWithString:self.text],
2062 if (_textInputClient == 0 && _autofillId != nil) {
2063 [
self.textInputDelegate flutterTextInputView:self
2064 updateEditingClient:_textInputClient
2066 withTag:_autofillId];
2068 [
self.textInputDelegate flutterTextInputView:self
2069 updateEditingClient:_textInputClient
2074- (void)updateEditingStateWithDelta:(
flutter::TextEditingDelta)delta {
2079 NSInteger composingBase = -1;
2080 NSInteger composingExtent = -1;
2081 if (
self.markedTextRange != nil) {
2086 NSDictionary* deltaToFramework = @{
2087 @"oldText" : @(
delta.old_text().c_str()),
2088 @"deltaText" : @(
delta.delta_text().c_str()),
2089 @"deltaStart" : @(
delta.delta_start()),
2090 @"deltaEnd" : @(
delta.delta_end()),
2091 @"selectionBase" : @(selectionBase),
2092 @"selectionExtent" : @(selectionExtent),
2094 @"selectionIsDirectional" : @(
false),
2095 @"composingBase" : @(composingBase),
2096 @"composingExtent" : @(composingExtent),
2099 [_pendingDeltas addObject:deltaToFramework];
2101 if (_pendingDeltas.count == 1) {
2103 dispatch_async(dispatch_get_main_queue(), ^{
2105 if (strongSelf && strongSelf.pendingDeltas.count > 0) {
2106 NSDictionary* deltas = @{
2107 @"deltas" : strongSelf.pendingDeltas,
2110 [strongSelf.textInputDelegate flutterTextInputView:strongSelf
2111 updateEditingClient:strongSelf->_textInputClient
2113 [strongSelf.pendingDeltas removeAllObjects];
2120 return self.text.length > 0;
2123- (void)insertText:(NSString*)text {
2124 if (
self.temporarilyDeletedComposedCharacter.length > 0 &&
text.length == 1 && !
text.UTF8String &&
2125 [
text characterAtIndex:0] == [
self.temporarilyDeletedComposedCharacter characterAtIndex:0]) {
2129 text =
self.temporarilyDeletedComposedCharacter;
2130 self.temporarilyDeletedComposedCharacter = nil;
2133 NSMutableArray<FlutterTextSelectionRect*>* copiedRects =
2134 [[NSMutableArray alloc] initWithCapacity:[_selectionRects count]];
2136 @"Expected a FlutterTextPosition for position (got %@).",
2139 for (NSUInteger
i = 0;
i < [_selectionRects count];
i++) {
2140 NSUInteger rectPosition = _selectionRects[i].position;
2141 if (rectPosition == insertPosition) {
2142 for (NSUInteger j = 0; j <=
text.length; j++) {
2149 if (rectPosition > insertPosition) {
2159 _scribbleFocusStatus = FlutterScribbleFocusStatusUnfocused;
2160 [
self resetScribbleInteractionStatusIfEnding];
2161 self.selectionRects = copiedRects;
2163 [
self replaceRange:_selectedTextRange withText:text];
2166- (UITextPlaceholder*)insertTextPlaceholderWithSize:(CGSize)size API_AVAILABLE(
ios(13.0)) {
2167 [
self.textInputDelegate flutterTextInputView:self
2168 insertTextPlaceholderWithSize:size
2169 withClient:_textInputClient];
2174- (void)removeTextPlaceholder:(UITextPlaceholder*)textPlaceholder API_AVAILABLE(
ios(13.0)) {
2176 [
self.textInputDelegate flutterTextInputView:self removeTextPlaceholder:_textInputClient];
2179- (void)deleteBackward {
2181 _scribbleFocusStatus = FlutterScribbleFocusStatusUnfocused;
2182 [
self resetScribbleInteractionStatusIfEnding];
2199 if (oldRange.location > 0) {
2200 NSRange newRange = NSMakeRange(oldRange.location - 1, 1);
2206 newRange = NSMakeRange(charRange.location, oldRange.location - charRange.location);
2218 NSString* deletedText = [
self.text substringWithRange:_selectedTextRange.range];
2220 self.temporarilyDeletedComposedCharacter =
2221 [deletedText substringWithRange:deleteFirstCharacterRange];
2223 [
self replaceRange:_selectedTextRange withText:@""];
2227- (void)postAccessibilityNotification:(UIAccessibilityNotifications)notification target:(
id)target {
2228 UIAccessibilityPostNotification(notification,
target);
2231- (void)accessibilityElementDidBecomeFocused {
2232 if ([
self accessibilityElementIsFocused]) {
2236 FML_DCHECK(_backingTextInputAccessibilityObject);
2237 [
self postAccessibilityNotification:UIAccessibilityScreenChangedNotification
2238 target:_backingTextInputAccessibilityObject];
2242- (
BOOL)accessibilityElementsHidden {
2243 return !_accessibilityEnabled;
2252#pragma mark - Key Events Handling
2253- (void)pressesBegan:(NSSet<UIPress*>*)presses
2254 withEvent:(UIPressesEvent*)event API_AVAILABLE(
ios(9.0)) {
2255 [_textInputPlugin.viewController pressesBegan:presses withEvent:event];
2258- (void)pressesChanged:(NSSet<UIPress*>*)presses
2259 withEvent:(UIPressesEvent*)event API_AVAILABLE(
ios(9.0)) {
2260 [_textInputPlugin.viewController pressesChanged:presses withEvent:event];
2263- (void)pressesEnded:(NSSet<UIPress*>*)presses
2264 withEvent:(UIPressesEvent*)event API_AVAILABLE(
ios(9.0)) {
2265 [_textInputPlugin.viewController pressesEnded:presses withEvent:event];
2268- (void)pressesCancelled:(NSSet<UIPress*>*)presses
2269 withEvent:(UIPressesEvent*)event API_AVAILABLE(
ios(9.0)) {
2270 [_textInputPlugin.viewController pressesCancelled:presses withEvent:event];
2299- (
BOOL)accessibilityElementsHidden {
2331@property(nonatomic, readonly)
2346 NSTimer* _enableFlutterTextInputViewAccessibilityTimer;
2350 self = [
super init];
2353 _textInputDelegate = textInputDelegate;
2354 _autofillContext = [[NSMutableDictionary alloc] init];
2356 _scribbleElements = [[NSMutableDictionary alloc] init];
2357 _keyboardViewContainer = [[UIView alloc] init];
2359 [[NSNotificationCenter defaultCenter] addObserver:self
2360 selector:@selector(handleKeyboardWillShow:)
2361 name:UIKeyboardWillShowNotification
2368- (void)handleKeyboardWillShow:(NSNotification*)notification {
2369 NSDictionary* keyboardInfo = [notification userInfo];
2370 NSValue* keyboardFrameEnd = [keyboardInfo valueForKey:UIKeyboardFrameEndUserInfoKey];
2371 _keyboardRect = [keyboardFrameEnd CGRectValue];
2375 [
self hideTextInput];
2378- (void)removeEnableFlutterTextInputViewAccessibilityTimer {
2379 if (_enableFlutterTextInputViewAccessibilityTimer) {
2380 [_enableFlutterTextInputViewAccessibilityTimer invalidate];
2381 _enableFlutterTextInputViewAccessibilityTimer = nil;
2385- (UIView<UITextInput>*)textInputView {
2390 NSString* method =
call.method;
2393 [
self showTextInput];
2395 }
else if ([method isEqualToString:
kHideMethod]) {
2396 [
self hideTextInput];
2399 [
self setTextInputClient:[args[0] intValue] withConfiguration:args[1]];
2403 [
self setPlatformViewTextInputClient];
2406 [
self setTextInputEditingState:args];
2409 [
self clearTextInputClient];
2412 [
self setEditableSizeAndTransform:args];
2415 [
self updateMarkedRect:args];
2418 [
self triggerAutofillSave:[args boolValue]];
2424 [
self setSelectionRects:args];
2427 [
self setSelectionRects:args];
2430 [
self startLiveTextInput];
2433 [
self updateConfig:args];
2436 CGFloat pointerY = (CGFloat)[
args[
@"pointerY"] doubleValue];
2437 [
self handlePointerMove:pointerY];
2440 CGFloat pointerY = (CGFloat)[
args[
@"pointerY"] doubleValue];
2441 [
self handlePointerUp:pointerY];
2448- (void)handlePointerUp:(CGFloat)pointerY {
2449 if (_keyboardView.superview != nil) {
2453 CGFloat screenHeight = screen.bounds.size.height;
2454 CGFloat keyboardHeight = _keyboardRect.size.height;
2456 BOOL shouldDismissKeyboardBasedOnVelocity = _pointerYVelocity < 0;
2457 [UIView animateWithDuration:kKeyboardAnimationTimeToCompleteion
2459 double keyboardDestination =
2460 shouldDismissKeyboardBasedOnVelocity ? screenHeight : screenHeight - keyboardHeight;
2461 _keyboardViewContainer.frame = CGRectMake(
2462 0, keyboardDestination, _viewController.flutterScreenIfViewLoaded.bounds.size.width,
2463 _keyboardViewContainer.frame.size.height);
2465 completion:^(BOOL finished) {
2466 if (shouldDismissKeyboardBasedOnVelocity) {
2467 [
self.textInputDelegate flutterTextInputView:self.activeView
2468 didResignFirstResponderWithTextInputClient:self.activeView.textInputClient];
2469 [
self dismissKeyboardScreenshot];
2471 [
self showKeyboardAndRemoveScreenshot];
2477- (void)dismissKeyboardScreenshot {
2478 for (UIView* subView in _keyboardViewContainer.subviews) {
2479 [subView removeFromSuperview];
2483- (void)showKeyboardAndRemoveScreenshot {
2484 [UIView setAnimationsEnabled:NO];
2485 [_cachedFirstResponder becomeFirstResponder];
2489 dispatch_get_main_queue(), ^{
2490 [UIView setAnimationsEnabled:YES];
2491 [
self dismissKeyboardScreenshot];
2495- (void)handlePointerMove:(CGFloat)pointerY {
2498 CGFloat screenHeight = screen.bounds.size.height;
2499 CGFloat keyboardHeight = _keyboardRect.size.height;
2500 if (screenHeight - keyboardHeight <= pointerY) {
2502 if (_keyboardView.superview == nil) {
2504 [
self takeKeyboardScreenshotAndDisplay];
2505 [
self hideKeyboardWithoutAnimationAndAvoidCursorDismissUpdate];
2507 [
self setKeyboardContainerHeight:pointerY];
2508 _pointerYVelocity = _previousPointerYPosition - pointerY;
2511 if (_keyboardView.superview != nil) {
2513 _keyboardViewContainer.frame = _keyboardRect;
2514 _pointerYVelocity = _previousPointerYPosition - pointerY;
2517 _previousPointerYPosition = pointerY;
2520- (void)setKeyboardContainerHeight:(CGFloat)pointerY {
2521 CGRect frameRect = _keyboardRect;
2522 frameRect.origin.y = pointerY;
2523 _keyboardViewContainer.frame = frameRect;
2526- (void)hideKeyboardWithoutAnimationAndAvoidCursorDismissUpdate {
2527 [UIView setAnimationsEnabled:NO];
2528 _cachedFirstResponder = UIApplication.sharedApplication.keyWindow.flutterFirstResponder;
2529 _activeView.preventCursorDismissWhenResignFirstResponder = YES;
2530 [_cachedFirstResponder resignFirstResponder];
2531 _activeView.preventCursorDismissWhenResignFirstResponder = NO;
2532 [UIView setAnimationsEnabled:YES];
2535- (void)takeKeyboardScreenshotAndDisplay {
2538 UIView* keyboardSnap = [screen snapshotViewAfterScreenUpdates:YES];
2539 keyboardSnap = [keyboardSnap resizableSnapshotViewFromRect:_keyboardRect
2540 afterScreenUpdates:YES
2541 withCapInsets:UIEdgeInsetsZero];
2542 _keyboardView = keyboardSnap;
2543 [_keyboardViewContainer addSubview:_keyboardView];
2544 if (_keyboardViewContainer.superview == nil) {
2545 [UIApplication.sharedApplication.delegate.window.rootViewController.view
2546 addSubview:_keyboardViewContainer];
2548 _keyboardViewContainer.layer.zPosition = NSIntegerMax;
2549 _keyboardViewContainer.frame = _keyboardRect;
2552- (
BOOL)showEditMenu:(NSDictionary*)args API_AVAILABLE(
ios(16.0)) {
2553 if (!
self.activeView.isFirstResponder) {
2556 NSDictionary<NSString*, NSNumber*>* encodedTargetRect =
args[@"targetRect"];
2557 CGRect globalTargetRect = CGRectMake(
2558 [encodedTargetRect[
@"x"] doubleValue], [encodedTargetRect[
@"y"] doubleValue],
2559 [encodedTargetRect[
@"width"] doubleValue], [encodedTargetRect[
@"height"] doubleValue]);
2560 CGRect localTargetRect = [
self.hostView convertRect:globalTargetRect toView:self.activeView];
2561 [
self.activeView showEditMenuWithTargetRect:localTargetRect];
2565- (void)hideEditMenu {
2566 [
self.activeView hideEditMenu];
2569- (void)setEditableSizeAndTransform:(NSDictionary*)dictionary {
2570 NSArray*
transform = dictionary[@"transform"];
2571 [_activeView setEditableTransform:transform];
2572 const int leftIndex = 12;
2573 const int topIndex = 13;
2578 [dictionary[
@"width"] intValue], [dictionary[
@"height"] intValue]);
2580 CGRectMake(0, 0, [dictionary[
@"width"] intValue], [dictionary[
@"height"] intValue]);
2581 _activeView.tintColor = [UIColor clearColor];
2586 if (@available(iOS 17, *)) {
2599- (void)updateMarkedRect:(NSDictionary*)dictionary {
2600 NSAssert(dictionary[
@"x"] != nil && dictionary[
@"y"] != nil && dictionary[
@"width"] != nil &&
2601 dictionary[
@"height"] != nil,
2602 @"Expected a dictionary representing a CGRect, got %@", dictionary);
2603 CGRect
rect = CGRectMake([dictionary[
@"x"] doubleValue], [dictionary[
@"y"] doubleValue],
2604 [dictionary[
@"width"] doubleValue], [dictionary[
@"height"] doubleValue]);
2608- (void)setSelectionRects:(NSArray*)encodedRects {
2609 NSMutableArray<FlutterTextSelectionRect*>* rectsAsRect =
2610 [[NSMutableArray alloc] initWithCapacity:[encodedRects count]];
2611 for (NSUInteger
i = 0;
i < [encodedRects count];
i++) {
2612 NSArray<NSNumber*>* encodedRect = encodedRects[i];
2614 selectionRectWithRect:CGRectMake([encodedRect[0] floatValue],
2615 [encodedRect[1] floatValue],
2616 [encodedRect[2] floatValue],
2617 [encodedRect[3] floatValue])
2618 position:[encodedRect[4] unsignedIntegerValue]
2619 writingDirection:[encodedRect[5] unsignedIntegerValue] == 1
2620 ? NSWritingDirectionLeftToRight
2621 : NSWritingDirectionRightToLeft]];
2627 _activeView.selectionRects = rectsAsRect;
2630- (void)startLiveTextInput {
2631 if (@available(iOS 15.0, *)) {
2632 if (_activeView == nil || !_activeView.isFirstResponder) {
2635 [_activeView captureTextFromCamera:nil];
2639- (void)showTextInput {
2640 _activeView.viewResponder = _viewResponder;
2641 [
self addToInputParentViewIfNeeded:_activeView];
2650 if (!_enableFlutterTextInputViewAccessibilityTimer) {
2651 _enableFlutterTextInputViewAccessibilityTimer =
2652 [NSTimer scheduledTimerWithTimeInterval:kUITextInputAccessibilityEnablingDelaySeconds
2654 selector:@selector(enableActiveViewAccessibility)
2658 [_activeView becomeFirstResponder];
2661- (void)enableActiveViewAccessibility {
2662 if (_activeView.isFirstResponder) {
2663 _activeView.accessibilityEnabled = YES;
2665 [
self removeEnableFlutterTextInputViewAccessibilityTimer];
2668- (void)hideTextInput {
2669 [
self removeEnableFlutterTextInputViewAccessibilityTimer];
2670 _activeView.accessibilityEnabled = NO;
2671 [_activeView resignFirstResponder];
2672 [_activeView removeFromSuperview];
2673 [_inputHider removeFromSuperview];
2676- (void)triggerAutofillSave:(
BOOL)saveEntries {
2677 [_activeView resignFirstResponder];
2682 [
self cleanUpViewHierarchy:YES clearText:YES delayRemoval:NO];
2683 [_autofillContext removeAllObjects];
2684 [
self changeInputViewsAutofillVisibility:YES];
2686 [_autofillContext removeAllObjects];
2689 [
self cleanUpViewHierarchy:YES clearText:!saveEntries delayRemoval:NO];
2690 [
self addToInputParentViewIfNeeded:_activeView];
2693- (void)setPlatformViewTextInputClient {
2697 [
self removeEnableFlutterTextInputViewAccessibilityTimer];
2698 _activeView.accessibilityEnabled = NO;
2699 [_activeView removeFromSuperview];
2700 [_inputHider removeFromSuperview];
2703- (void)setTextInputClient:(
int)client withConfiguration:(NSDictionary*)configuration {
2704 [
self resetAllClientIds];
2707 [
self changeInputViewsAutofillVisibility:NO];
2711 case kFlutterAutofillTypeNone:
2712 self.activeView = [
self createInputViewWith:configuration];
2714 case kFlutterAutofillTypeRegular:
2717 self.activeView = [
self updateAndShowAutofillViews:nil
2718 focusedField:configuration
2719 isPasswordRelated:NO];
2721 case kFlutterAutofillTypePassword:
2722 self.activeView = [
self updateAndShowAutofillViews:configuration[kAssociatedAutofillFields]
2723 focusedField:configuration
2724 isPasswordRelated:YES];
2727 [_activeView setTextInputClient:client];
2728 [_activeView reloadInputViews];
2740 [
self cleanUpViewHierarchy:NO clearText:YES delayRemoval:YES];
2751 [_autofillContext removeObjectForKey:autofillId];
2755 [
self addToInputParentViewIfNeeded:newView];
2759 if (autofillId &&
AutofillTypeOf(field) == kFlutterAutofillTypeNone) {
2760 [_autofillContext removeObjectForKey:autofillId];
2767 focusedField:(NSDictionary*)focusedField
2768 isPasswordRelated:(
BOOL)isPassword {
2771 NSAssert(focusedId,
@"autofillId must not be null for the focused field: %@", focusedField);
2776 focused = [
self getOrCreateAutofillableView:focusedField isPasswordAutofill:isPassword];
2777 [_autofillContext removeObjectForKey:focusedId];
2780 for (NSDictionary* field in fields) {
2782 NSAssert(autofillId,
@"autofillId must not be null for field: %@", field);
2785 BOOL isFocused = [focusedId isEqualToString:autofillId];
2788 focused = [
self getOrCreateAutofillableView:field isPasswordAutofill:isPassword];
2793 _autofillContext[autofillId] = isFocused ? focused
2794 : [
self getOrCreateAutofillableView:field
2795 isPasswordAutofill:isPassword];
2798 [_autofillContext removeObjectForKey:autofillId];
2802 NSAssert(focused,
@"The current focused input view must not be nil.");
2812 isPasswordAutofill:(
BOOL)needsPasswordAutofill {
2819 [
self addToInputParentViewIfNeeded:inputView];
2827- (UIView*)hostView {
2829 NSAssert(
host !=
nullptr,
2830 @"The application must have a host view since the keyboard client "
2831 @"must be part of the responder chain to function. The host view controller is %@",
2837- (NSArray<UIView*>*)textInputViews {
2838 return _inputHider.subviews;
2851- (void)cleanUpViewHierarchy:(
BOOL)includeActiveView
2852 clearText:(
BOOL)clearText
2853 delayRemoval:(
BOOL)delayRemoval {
2854 for (UIView* view in
self.textInputViews) {
2856 (includeActiveView || view != _activeView)) {
2858 if (_autofillContext[inputView.
autofillId] != view) {
2863 [inputView performSelector:@selector(removeFromSuperview) withObject:nil afterDelay:0.1];
2865 [inputView removeFromSuperview];
2874- (void)changeInputViewsAutofillVisibility:(
BOOL)newVisibility {
2875 for (UIView* view in
self.textInputViews) {
2890- (void)resetAllClientIds {
2891 for (UIView* view in
self.textInputViews) {
2900 if (![inputView isDescendantOfView:_inputHider]) {
2901 [_inputHider addSubview:inputView];
2910 UIView* parentView =
self.hostView;
2911 if (_inputHider.superview != parentView) {
2912 [parentView addSubview:_inputHider];
2916- (void)setTextInputEditingState:(NSDictionary*)state {
2917 [_activeView setTextInputState:state];
2920- (void)clearTextInputClient {
2921 [_activeView setTextInputClient:0];
2922 _activeView.frame = CGRectZero;
2925- (void)updateConfig:(NSDictionary*)dictionary {
2926 BOOL isSecureTextEntry = [dictionary[kSecureTextEntry] boolValue];
2927 for (UIView* view in
self.textInputViews) {
2934 if (inputView.isSecureTextEntry != isSecureTextEntry) {
2935 inputView.secureTextEntry = isSecureTextEntry;
2936 [inputView reloadInputViews];
2942#pragma mark UIIndirectScribbleInteractionDelegate
2944- (
BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction
2945 isElementFocused:(UIScribbleElementIdentifier)elementIdentifier
2946 API_AVAILABLE(
ios(14.0)) {
2947 return _activeView.scribbleFocusStatus == FlutterScribbleFocusStatusFocused;
2950- (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction
2951 focusElementIfNeeded:(UIScribbleElementIdentifier)elementIdentifier
2952 referencePoint:(CGPoint)focusReferencePoint
2953 completion:(
void (^)(UIResponder<UITextInput>* focusedInput))completion
2954 API_AVAILABLE(
ios(14.0)) {
2955 _activeView.scribbleFocusStatus = FlutterScribbleFocusStatusFocusing;
2956 [_indirectScribbleDelegate flutterTextInputPlugin:self
2957 focusElement:elementIdentifier
2958 atPoint:focusReferencePoint
2959 result:^(id _Nullable result) {
2960 _activeView.scribbleFocusStatus =
2961 FlutterScribbleFocusStatusFocused;
2962 completion(_activeView);
2966- (
BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction
2967 shouldDelayFocusForElement:(UIScribbleElementIdentifier)elementIdentifier
2968 API_AVAILABLE(
ios(14.0)) {
2972- (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction
2973 willBeginWritingInElement:(UIScribbleElementIdentifier)elementIdentifier
2974 API_AVAILABLE(
ios(14.0)) {
2977- (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction
2978 didFinishWritingInElement:(UIScribbleElementIdentifier)elementIdentifier
2979 API_AVAILABLE(
ios(14.0)) {
2982- (CGRect)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction
2983 frameForElement:(UIScribbleElementIdentifier)elementIdentifier
2984 API_AVAILABLE(
ios(14.0)) {
2985 NSValue* elementValue = [_scribbleElements objectForKey:elementIdentifier];
2986 if (elementValue == nil) {
2989 return [elementValue CGRectValue];
2992- (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction
2993 requestElementsInRect:(CGRect)rect
2995 (
void (^)(NSArray<UIScribbleElementIdentifier>* elements))completion
2996 API_AVAILABLE(
ios(14.0)) {
2997 [_indirectScribbleDelegate
2998 flutterTextInputPlugin:self
2999 requestElementsInRect:rect
3000 result:^(id _Nullable result) {
3001 NSMutableArray<UIScribbleElementIdentifier>* elements =
3002 [[NSMutableArray alloc] init];
3003 if ([result isKindOfClass:[NSArray class]]) {
3004 for (NSArray* elementArray in result) {
3005 [elements addObject:elementArray[0]];
3008 valueWithCGRect:CGRectMake(
3009 [elementArray[1] floatValue],
3010 [elementArray[2] floatValue],
3011 [elementArray[3] floatValue],
3012 [elementArray[4] floatValue])]
3013 forKey:elementArray[0]];
3016 completion(elements);
3020#pragma mark - Methods related to Scribble support
3024 if (@available(iOS 14.0, *)) {
3026 if (parentView != nil) {
3027 UIIndirectScribbleInteraction* scribbleInteraction = [[UIIndirectScribbleInteraction alloc]
3028 initWithDelegate:(id<UIIndirectScribbleInteractionDelegate>)self];
3029 [parentView addInteraction:scribbleInteraction];
3036- (void)resetViewResponder {
3037 _viewResponder = nil;
3041#pragma mark FlutterKeySecondaryResponder
3056- (
id)flutterFirstResponder {
3057 if (
self.isFirstResponder) {
3060 for (UIView* subView in
self.subviews) {
3061 UIView* firstResponder = subView.flutterFirstResponder;
3062 if (firstResponder) {
3063 return firstResponder;
void(^ FlutterResult)(id _Nullable result)
FLUTTER_DARWIN_EXPORT NSObject const * FlutterMethodNotImplemented
static const int points[]
static void copy(void *dst, const uint8_t *src, int width, int bpp, int deltaSrc, int offset, const SkPMColor ctable[])
BOOL preventCursorDismissWhenResignFirstResponder
instancetype initWithOwner:(FlutterTextInputPlugin *textInputPlugin)
BOOL isVisibleToAutofill()
id< UITextInputTokenizer > tokenizer()
void configureWithDictionary:(NSDictionary *configuration)
void replaceRangeLocal:withText:(NSRange range, [withText] NSString *text)
void setTextInputClient:(int client)
id< FlutterTextInputDelegate > textInputDelegate()
CGRect editMenuTargetRect
FlutterTextInputPlugin * textInputPlugin
BOOL accessibilityEnabled
CATransform3D editableTransform
NSString * temporarilyDeletedComposedCharacter
NSArray< UITextSelectionRect * > * rects()
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
#define FML_DCHECK(condition)
NSMethodSignature * methodSignatureForSelector:(SEL aSelector)
void enableActiveViewAccessibility()
UIView * keyboardViewContainer
NSMutableDictionary< NSString *, FlutterTextInputView * > * autofillContext
CGFloat previousPointerYPosition
FlutterTextInputViewAccessibilityHider * inputHider
FlutterTextInputView * activeView
UIView * cachedFirstResponder
instancetype positionWithIndex:(NSUInteger index)
instancetype positionWithIndex:affinity:(NSUInteger index,[affinity] UITextStorageDirection affinity)
UITextStorageDirection affinity
NSWritingDirection writingDirection
instancetype selectionRectWithRect:position:writingDirection:(CGRect rect,[position] NSUInteger position,[writingDirection] NSWritingDirection writingDirection)
instancetype selectionRectWithRectAndInfo:position:writingDirection:containsStart:containsEnd:isVertical:(CGRect rect,[position] NSUInteger position,[writingDirection] NSWritingDirection writingDirection,[containsStart] BOOL containsStart,[containsEnd] BOOL containsEnd,[isVertical] BOOL isVertical)
void enableActiveViewAccessibility()
FlutterTextInputPlugin * target
instancetype proxyWithTarget:(FlutterTextInputPlugin *target)
FlutterTextInputView * textInputView
fml::WeakNSObject< FlutterViewController > _viewController
fml::scoped_nsobject< FlutterTextInputPlugin > _textInputPlugin
CGRect localRectFromFrameworkTransform
void resetScribbleInteractionStatusIfEnding
API_AVAILABLE(ios(13.0)) @interface FlutterTextPlaceholder UITextRange * selectedTextRange
instancetype initWithOwner
id< FlutterViewResponder > viewResponder
CGRect caretRectForPosition
UIKeyboardAppearance keyboardAppearance
static NSString * AutofillIdFromDictionary(NSDictionary *dictionary)
static UITextAutocapitalizationType ToUITextAutoCapitalizationType(NSDictionary *type)
static NSString *const kSetMarkedTextRectMethod
static NSString *const kFinishAutofillContextMethod
bool _isFloatingCursorActive
static BOOL IsEmoji(NSString *text, NSRange charRange)
static NSString *const kAutofillHints
static NSString *const kAutofillEditingValue
FlutterTextRange * _selectedTextRange
static NSString *const kSecureTextEntry
bool _enableInteractiveSelection
static BOOL ShouldShowSystemKeyboard(NSDictionary *type)
static NSString *const kShowMethod
static NSString *const kUpdateConfigMethod
static UIKeyboardType ToUIKeyboardType(NSDictionary *type)
CGPoint _floatingCursorOffset
static NSString *const kSmartDashesType
static FLUTTER_ASSERT_ARC const char kTextAffinityDownstream[]
static constexpr double kUITextInputAccessibilityEnablingDelaySeconds
static NSString *const kSetSelectionRectsMethod
typedef NS_ENUM(NSInteger, FlutterAutofillType)
static NSString *const kAutocorrectionType
static NSString *const kSetPlatformViewClientMethod
static NSString *const kAssociatedAutofillFields
static NSString *const kKeyboardType
static const NSTimeInterval kKeyboardAnimationDelaySeconds
static NSString *const kSetEditingStateMethod
UIInputViewController * _inputViewController
static NSString *const kAutofillId
static NSString *const kOnInteractiveKeyboardPointerUpMethod
bool _isSystemKeyboardEnabled
static NSString *const kClearClientMethod
static NSString *const kKeyboardAppearance
const CGRect kInvalidFirstRect
static FlutterAutofillType AutofillTypeOf(NSDictionary *configuration)
static NSString *const kSmartQuotesType
static NSString *const kEnableDeltaModel
FlutterScribbleInteractionStatus _scribbleInteractionStatus
static NSString *const kSetClientMethod
static NSString *const kEnableInteractiveSelection
static NSString *const kInputAction
static NSString *const kStartLiveTextInputMethod
static NSString *const kAutofillProperties
static BOOL IsFieldPasswordRelated(NSDictionary *configuration)
static const NSTimeInterval kKeyboardAnimationTimeToCompleteion
static BOOL IsApproximatelyEqual(float x, float y, float delta)
static NSString *const kDeprecatedSetSelectionRectsMethod
static const char kTextAffinityUpstream[]
static UIReturnKeyType ToUIReturnKeyType(NSString *inputType)
static BOOL IsSelectionRectBoundaryCloserToPoint(CGPoint point, CGRect selectionRect, BOOL selectionRectIsRTL, BOOL useTrailingBoundaryOfSelectionRect, CGRect otherSelectionRect, BOOL otherSelectionRectIsRTL, CGFloat verticalPrecision)
static NSString *const kHideMethod
static UITextContentType ToUITextContentType(NSArray< NSString * > *hints)
static NSString *const kSetEditableSizeAndTransformMethod
static NSString *const kOnInteractiveKeyboardPointerMoveMethod
const char * _selectionAffinity
CATransform3D _editableTransform
sk_sp< SkBlender > blender SkRect rect
SK_API sk_sp< SkSurface > ios(9.0)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service host
NSRange RangeForCharacterAtIndex(NSString *text, NSUInteger index)
API_AVAILABLE(ios(14.0), macos(11.0)) static NSString *MTLCommandEncoderErrorStateToString(MTLCommandEncoderErrorState state)
static SkColor4f transform(SkColor4f c, SkColorSpace *src, SkColorSpace *dst)