From 053b17e1a20ac5b0a9cc613ef8f40e76c99a14eb Mon Sep 17 00:00:00 2001 From: LongCat is Looong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Thu, 2 Apr 2020 11:50:05 -0700 Subject: [PATCH 01/10] implementation --- .../ios/framework/Source/FlutterEngine.mm | 5 + .../Source/FlutterTextInputDelegate.h | 1 + .../Source/FlutterTextInputPlugin.mm | 151 +++++++++++++----- 3 files changed, 114 insertions(+), 43 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 4ed3ece0b98dc..fa5f8406fd41c 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -522,6 +522,11 @@ - (void)updateEditingClient:(int)client withState:(NSDictionary*)state { arguments:@[ @(client), state ]]; } +- (void)updateEditingClient:(int)client withState:(NSDictionary*)state withTag:(NSString*)tag { + [_textInputChannel.get() invokeMethod:@"TextInputClient.updateEditingStateWithTag" + arguments:@[ @(client), state, tag ]]; +} + - (void)updateFloatingCursor:(FlutterFloatingCursorDragState)state withClient:(int)client withPosition:(NSDictionary*)position { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h index d3451b5bd4cd4..f426c08e772c9 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h @@ -30,6 +30,7 @@ typedef NS_ENUM(NSInteger, FlutterFloatingCursorDragState) { @protocol FlutterTextInputDelegate - (void)updateEditingClient:(int)client withState:(NSDictionary*)state; +- (void)updateEditingClient:(int)client withState:(NSDictionary*)state withTag:(NSString*)tag; - (void)performAction:(FlutterTextInputAction)action withClient:(int)client; - (void)updateFloatingCursor:(FlutterFloatingCursorDragState)state withClient:(int)client diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 029932489f4fd..c1c869947da45 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -88,6 +88,13 @@ static UIReturnKeyType ToUIReturnKeyType(NSString* inputType) { return UIReturnKeyDefault; } +static NSString* _uniqueIdFromDictionary(NSDictionary* dictionary) { + NSDictionary* autofill = dictionary[@"autofill"]; + if (autofill == nil) + return nil; + return autofill[@"uniqueIdentifier"]; +} + #pragma mark - FlutterTextPosition @implementation FlutterTextPosition @@ -167,6 +174,7 @@ @interface FlutterTextInputView : UIView @end @implementation FlutterTextInputView { + NSString* _uniqueIdentifier; int _textInputClient; const char* _selectionAffinity; FlutterTextRange* _selectedTextRange; @@ -210,6 +218,8 @@ - (void)dealloc { [_markedTextRange release]; [_selectedTextRange release]; [_tokenizer release]; + if (_uniqueIdentifier != nil) + [_uniqueIdentifier release]; [super dealloc]; } @@ -267,7 +277,7 @@ - (NSRange)clampSelection:(NSRange)range forText:(NSString*)text { #pragma mark - UIResponder Overrides - (BOOL)canBecomeFirstResponder { - return YES; + return _textInputClient != 0; } #pragma mark - UITextInput Overrides @@ -616,16 +626,23 @@ - (void)updateEditingState { composingBase = ((FlutterTextPosition*)self.markedTextRange.start).index; composingExtent = ((FlutterTextPosition*)self.markedTextRange.end).index; } - [_textInputDelegate updateEditingClient:_textInputClient - withState:@{ - @"selectionBase" : @(selectionBase), - @"selectionExtent" : @(selectionExtent), - @"selectionAffinity" : @(_selectionAffinity), - @"selectionIsDirectional" : @(false), - @"composingBase" : @(composingBase), - @"composingExtent" : @(composingExtent), - @"text" : [NSString stringWithString:self.text], - }]; + + NSDictionary* state = @{ + @"selectionBase" : @(selectionBase), + @"selectionExtent" : @(selectionExtent), + @"selectionAffinity" : @(_selectionAffinity), + @"selectionIsDirectional" : @(false), + @"composingBase" : @(composingBase), + @"composingExtent" : @(composingExtent), + @"text" : [NSString stringWithString:self.text], + }; + + if (_textInputClient == 0 && _uniqueIdentifier != nil) + [_textInputDelegate updateEditingClient:_textInputClient + withState:state + withTag:_uniqueIdentifier]; + else + [_textInputDelegate updateEditingClient:_textInputClient withState:state]; } - (BOOL)hasText { @@ -688,8 +705,10 @@ - (BOOL)accessibilityElementsHidden { @end @implementation FlutterTextInputPlugin { - FlutterTextInputView* _view; - FlutterTextInputView* _secureView; + FlutterTextInputView* _nonAutofillInputView; + FlutterTextInputView* _nonAutofillSecureInputView; + + NSMutableArray* _inputViews; FlutterTextInputView* _activeView; FlutterTextInputViewAccessibilityHider* _inputHider; } @@ -700,12 +719,13 @@ - (instancetype)init { self = [super init]; if (self) { - _view = [[FlutterTextInputView alloc] init]; - _view.secureTextEntry = NO; - _secureView = [[FlutterTextInputView alloc] init]; - _secureView.secureTextEntry = YES; + _nonAutofillInputView = [[FlutterTextInputView alloc] init]; + _nonAutofillInputView.secureTextEntry = NO; + _nonAutofillInputView = [[FlutterTextInputView alloc] init]; + _nonAutofillSecureInputView.secureTextEntry = YES; + _inputViews = [[NSMutableArray alloc] init]; - _activeView = _view; + _activeView = _nonAutofillInputView; _inputHider = [[FlutterTextInputViewAccessibilityHider alloc] init]; } @@ -714,9 +734,10 @@ - (instancetype)init { - (void)dealloc { [self hideTextInput]; - [_view release]; - [_secureView release]; + [_nonAutofillInputView release]; + [_nonAutofillSecureInputView release]; [_inputHider release]; + [_inputViews release]; [super dealloc]; } @@ -749,58 +770,103 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } - (void)showTextInput { - NSAssert([UIApplication sharedApplication].keyWindow != nullptr, + UIWindow* keyWindow = [UIApplication sharedApplication].keyWindow; + NSAssert(keyWindow != nullptr, @"The application must have a key window since the keyboard client " @"must be part of the responder chain to function"); _activeView.textInputDelegate = _textInputDelegate; - [_inputHider addSubview:_activeView]; - [[UIApplication sharedApplication].keyWindow addSubview:_inputHider]; + if (![_activeView isDescendantOfView:_inputHider]) { + [_inputHider addSubview:_activeView]; + } + [keyWindow addSubview:_inputHider]; [_activeView becomeFirstResponder]; } - (void)hideTextInput { [_activeView resignFirstResponder]; - [_activeView removeFromSuperview]; [_inputHider removeFromSuperview]; } - (void)setTextInputClient:(int)client withConfiguration:(NSDictionary*)configuration { - NSDictionary* inputType = configuration[@"inputType"]; - NSString* keyboardAppearance = configuration[@"keyboardAppearance"]; - if ([configuration[@"obscureText"] boolValue]) { - _activeView = _secureView; + NSArray* allFields = configuration[@"allFields"]; + NSString* clientUniqueId = _uniqueIdFromDictionary(configuration); + bool isSecureTextEntry = [configuration[@"obscureText"] boolValue]; + + if (allFields == nil) { + _activeView = isSecureTextEntry ? _nonAutofillSecureInputView : _nonAutofillInputView; + [FlutterTextInputPlugin setupInputView:_activeView WithConfiguration:configuration]; + + if (![_activeView isDescendantOfView:_inputHider]) { + [_inputHider addSubview:_activeView]; + } } else { - _activeView = _view; + NSAssert(clientUniqueId != nil, @"The client's unique id can't be null"); + for (FlutterTextInputView* v in _inputViews) + [v removeFromSuperview]; + for (UIView* subview in [_inputHider subviews]) + [subview removeFromSuperview]; + + [_inputViews removeAllObjects]; + + for (NSDictionary* field in allFields) { + FlutterTextInputView* newInputView = [[FlutterTextInputView alloc] init]; + [_inputViews addObject:newInputView]; + + if ([clientUniqueId isEqualToString:_uniqueIdFromDictionary(field)]) + _activeView = newInputView; + + [FlutterTextInputPlugin setupInputView:newInputView WithConfiguration:field]; + [_inputHider addSubview:newInputView]; + } } - _activeView.keyboardType = ToUIKeyboardType(inputType); - _activeView.returnKeyType = ToUIReturnKeyType(configuration[@"inputAction"]); - _activeView.autocapitalizationType = ToUITextAutoCapitalizationType(configuration); + [_activeView setTextInputClient:client]; + [_activeView reloadInputViews]; +} + ++ (void)setupInputView:(FlutterTextInputView*)inputView + WithConfiguration:(NSDictionary*)configuration { + NSDictionary* inputType = configuration[@"inputType"]; + NSString* keyboardAppearance = configuration[@"keyboardAppearance"]; + NSDictionary* autofill = configuration[@"autofill"]; + + inputView.secureTextEntry = [configuration[@"obscureText"] boolValue]; + inputView.keyboardType = ToUIKeyboardType(inputType); + inputView.returnKeyType = ToUIReturnKeyType(configuration[@"inputAction"]); + inputView.autocapitalizationType = ToUITextAutoCapitalizationType(configuration); + if (@available(iOS 11.0, *)) { NSString* smartDashesType = configuration[@"smartDashesType"]; // This index comes from the SmartDashesType enum in the framework. bool smartDashesIsDisabled = smartDashesType && [smartDashesType isEqualToString:@"0"]; - _activeView.smartDashesType = + inputView.smartDashesType = smartDashesIsDisabled ? UITextSmartDashesTypeNo : UITextSmartDashesTypeYes; NSString* smartQuotesType = configuration[@"smartQuotesType"]; // This index comes from the SmartQuotesType enum in the framework. bool smartQuotesIsDisabled = smartQuotesType && [smartQuotesType isEqualToString:@"0"]; - _activeView.smartQuotesType = + inputView.smartQuotesType = smartQuotesIsDisabled ? UITextSmartQuotesTypeNo : UITextSmartQuotesTypeYes; } if ([keyboardAppearance isEqualToString:@"Brightness.dark"]) { - _activeView.keyboardAppearance = UIKeyboardAppearanceDark; + inputView.keyboardAppearance = UIKeyboardAppearanceDark; } else if ([keyboardAppearance isEqualToString:@"Brightness.light"]) { - _activeView.keyboardAppearance = UIKeyboardAppearanceLight; + inputView.keyboardAppearance = UIKeyboardAppearanceLight; } else { - _activeView.keyboardAppearance = UIKeyboardAppearanceDefault; + inputView.keyboardAppearance = UIKeyboardAppearanceDefault; } NSString* autocorrect = configuration[@"autocorrect"]; - _activeView.autocorrectionType = autocorrect && ![autocorrect boolValue] - ? UITextAutocorrectionTypeNo - : UITextAutocorrectionTypeDefault; - [_activeView setTextInputClient:client]; - [_activeView reloadInputViews]; + inputView.autocorrectionType = autocorrect && ![autocorrect boolValue] + ? UITextAutocorrectionTypeNo + : UITextAutocorrectionTypeDefault; + if (autofill == nil) { + if (@available(iOS 10.0, *)) + inputView.textContentType = @""; + } else { + if (@available(iOS 10.0, *)) + inputView.textContentType = autofill[@"hints"]; + + [inputView setTextInputState:autofill[@"editingValue"]]; + } } - (void)setTextInputEditingState:(NSDictionary*)state { @@ -810,5 +876,4 @@ - (void)setTextInputEditingState:(NSDictionary*)state { - (void)clearTextInputClient { [_activeView setTextInputClient:0]; } - @end From 60af9a59ae497c4155d08f98e11479668d2d6c31 Mon Sep 17 00:00:00 2001 From: LongCat is Looong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Thu, 2 Apr 2020 17:00:47 -0700 Subject: [PATCH 02/10] add test --- .../ios/framework/Source/FlutterEngine.mm | 2 +- .../framework/Source/FlutterTextInputPlugin.h | 36 +++++++++ .../Source/FlutterTextInputPlugin.mm | 78 ++++++++---------- .../Source/FlutterTextInputPluginTest.m | 79 +++++++++++++++++++ .../IosUnitTests.xcodeproj/project.pbxproj | 4 + 5 files changed, 154 insertions(+), 45 deletions(-) create mode 100644 shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 613176501d2dd..bf1314b9e6828 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -536,7 +536,7 @@ - (void)updateEditingClient:(int)client withState:(NSDictionary*)state { - (void)updateEditingClient:(int)client withState:(NSDictionary*)state withTag:(NSString*)tag { [_textInputChannel.get() invokeMethod:@"TextInputClient.updateEditingStateWithTag" - arguments:@[ @(client), state, tag ]]; + arguments:@[ @(client), @{tag : state} ]]; } - (void)updateFloatingCursor:(FlutterFloatingCursorDragState)state diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 70bd949579ce2..6f9511391e9b5 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -10,6 +10,9 @@ #include "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" #include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h" +#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG +FLUTTER_EXPORT +#endif @interface FlutterTextInputPlugin : NSObject @property(nonatomic, assign) id textInputDelegate; @@ -36,6 +39,9 @@ @end /** A range of text in the buffer of a Flutter text editing widget. */ +#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG +FLUTTER_EXPORT +#endif @interface FlutterTextRange : UITextRange @property(nonatomic, readonly) NSRange range; @@ -44,4 +50,34 @@ @end +#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG +FLUTTER_EXPORT +#endif +@interface FlutterTextInputView : UIView + +// UITextInput +@property(nonatomic, readonly) NSMutableString* text; +@property(nonatomic, readonly) NSMutableString* markedText; +@property(readwrite, copy) UITextRange* selectedTextRange; +@property(nonatomic, strong) UITextRange* markedTextRange; +@property(nonatomic, copy) NSDictionary* markedTextStyle; +@property(nonatomic, assign) id inputDelegate; + +// UITextInputTraits +@property(nonatomic) UITextAutocapitalizationType autocapitalizationType; +@property(nonatomic) UITextAutocorrectionType autocorrectionType; +@property(nonatomic) UITextSpellCheckingType spellCheckingType; +@property(nonatomic) BOOL enablesReturnKeyAutomatically; +@property(nonatomic) UIKeyboardAppearance keyboardAppearance; +@property(nonatomic) UIKeyboardType keyboardType; +@property(nonatomic) UIReturnKeyType returnKeyType; +@property(nonatomic, getter=isSecureTextEntry) BOOL secureTextEntry; +@property(nonatomic) UITextSmartQuotesType smartQuotesType API_AVAILABLE(ios(11.0)); +@property(nonatomic) UITextSmartDashesType smartDashesType API_AVAILABLE(ios(11.0)); +@property(nonatomic, copy) UITextContentType textContentType API_AVAILABLE(ios(10.0)); + +@property(nonatomic, assign) id textInputDelegate; + +@end + #endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTPLUGIN_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index c1c869947da45..0b3e40b0b5415 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -88,11 +88,18 @@ static UIReturnKeyType ToUIReturnKeyType(NSString* inputType) { return UIReturnKeyDefault; } +// TODO(LongCatIsLooong): add translation for evey predefined +// UITextContentType. +static UITextContentType ToUITextContentType(NSArray* hints) { + if (hints == nil || hints.count == 0) + return @""; + + return hints[0]; +} + static NSString* _uniqueIdFromDictionary(NSDictionary* dictionary) { NSDictionary* autofill = dictionary[@"autofill"]; - if (autofill == nil) - return nil; - return autofill[@"uniqueIdentifier"]; + return autofill == nil ? nil : autofill[@"uniqueIdentifier"]; } #pragma mark - FlutterTextPosition @@ -147,34 +154,8 @@ - (id)copyWithZone:(NSZone*)zone { @end -@interface FlutterTextInputView : UIView - -// UITextInput -@property(nonatomic, readonly) NSMutableString* text; -@property(nonatomic, readonly) NSMutableString* markedText; -@property(readwrite, copy) UITextRange* selectedTextRange; -@property(nonatomic, strong) UITextRange* markedTextRange; -@property(nonatomic, copy) NSDictionary* markedTextStyle; -@property(nonatomic, assign) id inputDelegate; - -// UITextInputTraits -@property(nonatomic) UITextAutocapitalizationType autocapitalizationType; -@property(nonatomic) UITextAutocorrectionType autocorrectionType; -@property(nonatomic) UITextSpellCheckingType spellCheckingType; -@property(nonatomic) BOOL enablesReturnKeyAutomatically; -@property(nonatomic) UIKeyboardAppearance keyboardAppearance; -@property(nonatomic) UIKeyboardType keyboardType; -@property(nonatomic) UIReturnKeyType returnKeyType; -@property(nonatomic, getter=isSecureTextEntry) BOOL secureTextEntry; -@property(nonatomic) UITextSmartQuotesType smartQuotesType API_AVAILABLE(ios(11.0)); -@property(nonatomic) UITextSmartDashesType smartDashesType API_AVAILABLE(ios(11.0)); - -@property(nonatomic, assign) id textInputDelegate; - -@end - @implementation FlutterTextInputView { - NSString* _uniqueIdentifier; + NSString* _autofillId; int _textInputClient; const char* _selectionAffinity; FlutterTextRange* _selectedTextRange; @@ -218,8 +199,7 @@ - (void)dealloc { [_markedTextRange release]; [_selectedTextRange release]; [_tokenizer release]; - if (_uniqueIdentifier != nil) - [_uniqueIdentifier release]; + [_autofillId release]; [super dealloc]; } @@ -227,6 +207,10 @@ - (void)setTextInputClient:(int)client { _textInputClient = client; } +- (void)setAutofillId:(NSString*)autofillId { + _autofillId = [autofillId copy]; +} + - (void)setTextInputState:(NSDictionary*)state { NSString* newText = state[@"text"]; BOOL textChanged = ![self.text isEqualToString:newText]; @@ -637,10 +621,8 @@ - (void)updateEditingState { @"text" : [NSString stringWithString:self.text], }; - if (_textInputClient == 0 && _uniqueIdentifier != nil) - [_textInputDelegate updateEditingClient:_textInputClient - withState:state - withTag:_uniqueIdentifier]; + if (_textInputClient == 0 && _autofillId != nil) + [_textInputDelegate updateEditingClient:_textInputClient withState:state withTag:_autofillId]; else [_textInputDelegate updateEditingClient:_textInputClient withState:state]; } @@ -810,9 +792,13 @@ - (void)setTextInputClient:(int)client withConfiguration:(NSDictionary*)configur for (NSDictionary* field in allFields) { FlutterTextInputView* newInputView = [[FlutterTextInputView alloc] init]; + newInputView.textInputDelegate = _textInputDelegate; [_inputViews addObject:newInputView]; - if ([clientUniqueId isEqualToString:_uniqueIdFromDictionary(field)]) + NSString* autofillId = _uniqueIdFromDictionary(field); + [newInputView setAutofillId:autofillId]; + + if ([clientUniqueId isEqualToString:autofillId]) _activeView = newInputView; [FlutterTextInputPlugin setupInputView:newInputView WithConfiguration:field]; @@ -858,14 +844,17 @@ + (void)setupInputView:(FlutterTextInputView*)inputView inputView.autocorrectionType = autocorrect && ![autocorrect boolValue] ? UITextAutocorrectionTypeNo : UITextAutocorrectionTypeDefault; - if (autofill == nil) { - if (@available(iOS 10.0, *)) + if (@available(iOS 10.0, *)) { + if (autofill == nil) { inputView.textContentType = @""; - } else { - if (@available(iOS 10.0, *)) - inputView.textContentType = autofill[@"hints"]; - - [inputView setTextInputState:autofill[@"editingValue"]]; + } else { + inputView.textContentType = ToUITextContentType(autofill[@"hints"]); + [inputView setTextInputState:autofill[@"editingValue"]]; + // An input field needs to be visible in order to get + // autofilled when it's not the one that triggered + // autofill. + inputView.frame = CGRectMake(0, 0, 1, 1); + } } } @@ -876,4 +865,5 @@ - (void)setTextInputEditingState:(NSDictionary*)state { - (void)clearTextInputClient { [_activeView setTextInputClient:0]; } + @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m new file mode 100644 index 0000000000000..5d797c5dbdf03 --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m @@ -0,0 +1,79 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import +#include "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" +#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h" +#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" + +FLUTTER_ASSERT_ARC + +@interface FlutterTextInputPluginTest : XCTestCase +@end + +@implementation FlutterTextInputPluginTest + +- (void)testAutofillInputViews { + // Setup test. + id engine = OCMClassMock([FlutterEngine class]); + FlutterTextInputPlugin* textInputPlugin = [[FlutterTextInputPlugin alloc] init]; + textInputPlugin.textInputDelegate = engine; + + NSDictionary* template = @{ + @"inputType" : @{@"name" : @"TextInuptType.text"}, + @"keyboardAppearance" : @"Brightness.light", + @"obscureText" : @NO, + @"inputAction" : @"TextInputAction.unspecified", + @"smartDashesType" : @"0", + @"smartQuotesType" : @"0", + @"autocorrect" : @YES + }; + + NSMutableDictionary* field1 = [template mutableCopy]; + [field1 setValue:@{ + @"uniqueIdentifier" : @"field1", + @"hints" : @[ @"hint1" ], + @"editingValue" : @{@"text" : @""} + } + forKey:@"autofill"]; + + NSMutableDictionary* field2 = [template mutableCopy]; + [field2 setValue:@{ + @"uniqueIdentifier" : @"field2", + @"hints" : @[ @"hint2" ], + @"editingValue" : @{@"text" : @""} + } + forKey:@"autofill"]; + + NSMutableDictionary* config = [field1 mutableCopy]; + [config setValue:@[ field1, field2 ] forKey:@"allFields"]; + + FlutterMethodCall* setClientCall = + [FlutterMethodCall methodCallWithMethodName:@"TextInput.setClient" + arguments:@[ @123, config ]]; + + [textInputPlugin handleMethodCall:setClientCall + result:^(id _Nullable result){ + }]; + + // Find all input views in the input hider view. + NSArray* inputFields = + [[[textInputPlugin textInputView] superview] subviews]; + + XCTAssertEqual(inputFields.count, 2); + + // Find the inactive autofillable input field. + FlutterTextInputView* inactiveView = inputFields[1]; + [inactiveView replaceRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 0)] + withText:@"Autofilled!"]; + + // Verify behavior. + OCMVerify([engine updateEditingClient:0 withState:[OCMArg isNotNil] withTag:@"field2"]); + + // Clean up mocks + [engine stopMocking]; +} + +@end diff --git a/testing/ios/IosUnitTests/IosUnitTests.xcodeproj/project.pbxproj b/testing/ios/IosUnitTests/IosUnitTests.xcodeproj/project.pbxproj index 82f36199ac95a..ba76681120739 100644 --- a/testing/ios/IosUnitTests/IosUnitTests.xcodeproj/project.pbxproj +++ b/testing/ios/IosUnitTests/IosUnitTests.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ 0D6AB6EB22BB40E700EEE540 /* FlutterEngineTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0D6AB6E722BB40CF00EEE540 /* FlutterEngineTest.mm */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; 0D6AB72C22BC339F00EEE540 /* libOCMock.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D6AB72522BC336100EEE540 /* libOCMock.a */; }; 0D6AB73F22BD8F0200EEE540 /* FlutterEngineConfig.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 0D6AB73E22BD8F0200EEE540 /* FlutterEngineConfig.xcconfig */; }; + 3D3E3323243679E800DE8862 /* FlutterTextInputPluginTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D3E331C243679E800DE8862 /* FlutterTextInputPluginTest.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -94,6 +95,7 @@ 0D6AB6E722BB40CF00EEE540 /* FlutterEngineTest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FlutterEngineTest.mm; sourceTree = ""; }; 0D6AB71722BC336100EEE540 /* OCMock.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = OCMock.xcodeproj; path = ../../../../../third_party/ocmock/Source/OCMock.xcodeproj; sourceTree = ""; }; 0D6AB73E22BD8F0200EEE540 /* FlutterEngineConfig.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = FlutterEngineConfig.xcconfig; sourceTree = ""; }; + 3D3E331C243679E800DE8862 /* FlutterTextInputPluginTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FlutterTextInputPluginTest.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -175,6 +177,7 @@ isa = PBXGroup; children = ( 0D52D3B622C566D50011DEBD /* FlutterBinaryMessengerRelayTest.mm */, + 3D3E331C243679E800DE8862 /* FlutterTextInputPluginTest.m */, 0D6AB6E722BB40CF00EEE540 /* FlutterEngineTest.mm */, 0D17A5BF22D78FCD0057279F /* FlutterViewControllerTest.m */, 0D4C3FAF22DF9F5300A67C70 /* FlutterPluginAppLifeCycleDelegateTest.m */, @@ -387,6 +390,7 @@ buildActionMask = 2147483647; files = ( 0D6AB6EB22BB40E700EEE540 /* FlutterEngineTest.mm in Sources */, + 3D3E3323243679E800DE8862 /* FlutterTextInputPluginTest.m in Sources */, 0D17A5C022D78FCD0057279F /* FlutterViewControllerTest.m in Sources */, 0D1CE5D8233430F400E5D880 /* FlutterChannelsTest.m in Sources */, 0D52D3BD22C566D50011DEBD /* FlutterBinaryMessengerRelayTest.mm in Sources */, From 0d195a62aa872fd48f7959a5ad8bd84ee62a4f23 Mon Sep 17 00:00:00 2001 From: LongCat is Looong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Thu, 2 Apr 2020 19:48:27 -0700 Subject: [PATCH 03/10] update --- ci/licenses_golden/licenses_flutter | 1 + .../darwin/ios/framework/Source/FlutterTextInputPlugin.mm | 3 +++ 2 files changed, 4 insertions(+) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 903a33b252e51..efada8efe6556 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -867,6 +867,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginA FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterUmbrellaImport.m FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 0b3e40b0b5415..f057ae956a98e 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -261,6 +261,9 @@ - (NSRange)clampSelection:(NSRange)range forText:(NSString*)text { #pragma mark - UIResponder Overrides - (BOOL)canBecomeFirstResponder { + // Only the currently focused input field can + // become the first responder. This prevents iOS + // from changing focus by itself. return _textInputClient != 0; } From e87f36fd5239e2d5302d87cd7718d789aeaa98b6 Mon Sep 17 00:00:00 2001 From: LongCat is Looong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Thu, 9 Apr 2020 19:52:34 -0700 Subject: [PATCH 04/10] expand hint list --- .../Source/FlutterTextInputPlugin.mm | 81 ++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index f057ae956a98e..795affe98c0ce 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -88,12 +88,89 @@ static UIReturnKeyType ToUIReturnKeyType(NSString* inputType) { return UIReturnKeyDefault; } -// TODO(LongCatIsLooong): add translation for evey predefined -// UITextContentType. static UITextContentType ToUITextContentType(NSArray* hints) { if (hints == nil || hints.count == 0) return @""; + NSString* hint = hints[0]; + if (@available(iOS 10.0, *)) { + if ([hint isEqualToString:@"addressCityAndState"]) + return UITextContentTypeAddressCityAndState; + + if ([hint isEqualToString:@"addressState"]) + return UITextContentTypeAddressState; + + if ([hint isEqualToString:@"addressCity"]) + return UITextContentTypeAddressCity; + + if ([hint isEqualToString:@"sublocality"]) + return UITextContentTypeSublocality; + + if ([hint isEqualToString:@"streetAddressLine1"]) + return UITextContentTypeStreetAddressLine1; + + if ([hint isEqualToString:@"streetAddressLine2"]) + return UITextContentTypeStreetAddressLine2; + + if ([hint isEqualToString:@"countryName"]) + return UITextContentTypeCountryName; + + if ([hint isEqualToString:@"fullStreetAddress"]) + return UITextContentTypeFullStreetAddress; + + if ([hint isEqualToString:@"postalCode"]) + return UITextContentTypePostalCode; + + if ([hint isEqualToString:@"location"]) + return UITextContentTypeLocation; + + if ([hint isEqualToString:@"creditCardNumber"]) + return UITextContentTypeCreditCardNumber; + + if ([hint isEqualToString:@"email"]) + return UITextContentTypeEmailAddress; + + if ([hint isEqualToString:@"jobTitle"]) + return UITextContentTypeJobTitle; + + if ([hint isEqualToString:@"givenName"]) + return UITextContentTypeGivenName; + + if ([hint isEqualToString:@"middleName"]) + return UITextContentTypeMiddleName; + + if ([hint isEqualToString:@"familyName"]) + return UITextContentTypeFamilyName; + + if ([hint isEqualToString:@"name"]) + return UITextContentTypeName; + + if ([hint isEqualToString:@"namePrefix"]) + return UITextContentTypeNamePrefix; + + if ([hint isEqualToString:@"nameSuffix"]) + return UITextContentTypeNameSuffix; + + if ([hint isEqualToString:@"nickname"]) + return UITextContentTypeNickname; + + if ([hint isEqualToString:@"organizationName"]) + return UITextContentTypeOrganizationName; + + if ([hint isEqualToString:@"telephoneNumber"]) + return UITextContentTypeTelephoneNumber; + } + + if (@available(iOS 11.0, *)) { + if ([hint isEqualToString:@"password"]) + return UITextContentTypePassword; + } + + if (@available(iOS 12.0, *)) { + if ([hint isEqualToString:@"oneTimeCode"]) + return UITextContentTypeOneTimeCode; + } + return hints[0]; } From 3817e3d66b99a314edbb155789f359c6a0608674 Mon Sep 17 00:00:00 2001 From: LongCat is Looong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Fri, 10 Apr 2020 10:41:15 -0700 Subject: [PATCH 05/10] formatting --- .../darwin/ios/framework/Source/FlutterTextInputPlugin.h | 1 - .../darwin/ios/framework/Source/FlutterTextInputPluginTest.m | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 4ace2934da51d..3c4f89d3d8011 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -76,7 +76,6 @@ FLUTTER_EXPORT @property(nonatomic) UITextSmartDashesType smartDashesType API_AVAILABLE(ios(11.0)); @property(nonatomic, copy) UITextContentType textContentType API_AVAILABLE(ios(10.0)); - @property(nonatomic, assign) id textInputDelegate; @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m index 3808dfe2a200c..aeb562e15383a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m @@ -71,7 +71,7 @@ - (void)testAutofillInputViews { // Verify behavior. OCMVerify([engine updateEditingClient:0 withState:[OCMArg isNotNil] withTag:@"field2"]); - + // Clean up mocks [engine stopMocking]; } From 1f3b53b5c6b62b3ceffc311eda29784ae3c70ebb Mon Sep 17 00:00:00 2001 From: LongCat is Looong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Mon, 13 Apr 2020 11:21:55 -0700 Subject: [PATCH 06/10] s/allFields/fields/ --- .../darwin/ios/framework/Source/FlutterTextInputPlugin.mm | 6 +++--- .../ios/framework/Source/FlutterTextInputPluginTest.m | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 4cf6dbe8f2299..88423604a4d3d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -860,11 +860,11 @@ - (void)hideTextInput { } - (void)setTextInputClient:(int)client withConfiguration:(NSDictionary*)configuration { - NSArray* allFields = configuration[@"allFields"]; + NSArray* fields = configuration[@"fields"]; NSString* clientUniqueId = _uniqueIdFromDictionary(configuration); bool isSecureTextEntry = [configuration[@"obscureText"] boolValue]; - if (allFields == nil) { + if (fields == nil) { _activeView = isSecureTextEntry ? _nonAutofillSecureInputView : _nonAutofillInputView; [FlutterTextInputPlugin setupInputView:_activeView WithConfiguration:configuration]; @@ -880,7 +880,7 @@ - (void)setTextInputClient:(int)client withConfiguration:(NSDictionary*)configur [_inputViews removeAllObjects]; - for (NSDictionary* field in allFields) { + for (NSDictionary* field in fields) { FlutterTextInputView* newInputView = [[FlutterTextInputView alloc] init]; newInputView.textInputDelegate = _textInputDelegate; [_inputViews addObject:newInputView]; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m index aeb562e15383a..6dc2bd7eaae19 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m @@ -48,7 +48,7 @@ - (void)testAutofillInputViews { forKey:@"autofill"]; NSMutableDictionary* config = [field1 mutableCopy]; - [config setValue:@[ field1, field2 ] forKey:@"allFields"]; + [config setValue:@[ field1, field2 ] forKey:@"fields"]; FlutterMethodCall* setClientCall = [FlutterMethodCall methodCallWithMethodName:@"TextInput.setClient" From 7a4bef48e05b338b14e86cbac8b61db706ee8b56 Mon Sep 17 00:00:00 2001 From: LongCat is Looong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Mon, 13 Apr 2020 12:50:25 -0700 Subject: [PATCH 07/10] review --- .../Source/FlutterTextInputPlugin.mm | 85 ++++++++++++------- 1 file changed, 56 insertions(+), 29 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 88423604a4d3d..c07c3b4a3fa31 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -94,81 +94,105 @@ static UITextContentType ToUITextContentType(NSArray* hints) { NSString* hint = hints[0]; if (@available(iOS 10.0, *)) { - if ([hint isEqualToString:@"addressCityAndState"]) + if ([hint isEqualToString:@"addressCityAndState"]) { return UITextContentTypeAddressCityAndState; + } - if ([hint isEqualToString:@"addressState"]) + if ([hint isEqualToString:@"addressState"]) { return UITextContentTypeAddressState; + } - if ([hint isEqualToString:@"addressCity"]) + if ([hint isEqualToString:@"addressCity"]) { return UITextContentTypeAddressCity; + } - if ([hint isEqualToString:@"sublocality"]) + if ([hint isEqualToString:@"sublocality"]) { return UITextContentTypeSublocality; + } - if ([hint isEqualToString:@"streetAddressLine1"]) + if ([hint isEqualToString:@"streetAddressLine1"]) { return UITextContentTypeStreetAddressLine1; + } - if ([hint isEqualToString:@"streetAddressLine2"]) + if ([hint isEqualToString:@"streetAddressLine2"]) { return UITextContentTypeStreetAddressLine2; + } - if ([hint isEqualToString:@"countryName"]) + if ([hint isEqualToString:@"countryName"]) { return UITextContentTypeCountryName; + } - if ([hint isEqualToString:@"fullStreetAddress"]) + if ([hint isEqualToString:@"fullStreetAddress"]) { return UITextContentTypeFullStreetAddress; + } - if ([hint isEqualToString:@"postalCode"]) + if ([hint isEqualToString:@"postalCode"]) { return UITextContentTypePostalCode; + } - if ([hint isEqualToString:@"location"]) + if ([hint isEqualToString:@"location"]) { return UITextContentTypeLocation; + } - if ([hint isEqualToString:@"creditCardNumber"]) + if ([hint isEqualToString:@"creditCardNumber"]) { return UITextContentTypeCreditCardNumber; + } - if ([hint isEqualToString:@"email"]) + if ([hint isEqualToString:@"email"]) { return UITextContentTypeEmailAddress; + } - if ([hint isEqualToString:@"jobTitle"]) + if ([hint isEqualToString:@"jobTitle"]) { return UITextContentTypeJobTitle; + } - if ([hint isEqualToString:@"givenName"]) + if ([hint isEqualToString:@"givenName"]) { return UITextContentTypeGivenName; + } - if ([hint isEqualToString:@"middleName"]) + if ([hint isEqualToString:@"middleName"]) { return UITextContentTypeMiddleName; + } - if ([hint isEqualToString:@"familyName"]) + if ([hint isEqualToString:@"familyName"]) { return UITextContentTypeFamilyName; + } - if ([hint isEqualToString:@"name"]) + if ([hint isEqualToString:@"name"]) { return UITextContentTypeName; + } - if ([hint isEqualToString:@"namePrefix"]) + if ([hint isEqualToString:@"namePrefix"]) { return UITextContentTypeNamePrefix; + } - if ([hint isEqualToString:@"nameSuffix"]) + if ([hint isEqualToString:@"nameSuffix"]) { return UITextContentTypeNameSuffix; + } - if ([hint isEqualToString:@"nickname"]) + if ([hint isEqualToString:@"nickname"]) { return UITextContentTypeNickname; + } - if ([hint isEqualToString:@"organizationName"]) + if ([hint isEqualToString:@"organizationName"]) { return UITextContentTypeOrganizationName; + } - if ([hint isEqualToString:@"telephoneNumber"]) + if ([hint isEqualToString:@"telephoneNumber"]) { return UITextContentTypeTelephoneNumber; + } } if (@available(iOS 11.0, *)) { - if ([hint isEqualToString:@"password"]) + if ([hint isEqualToString:@"password"]) { return UITextContentTypePassword; + } } if (@available(iOS 12.0, *)) { - if ([hint isEqualToString:@"oneTimeCode"]) + if ([hint isEqualToString:@"oneTimeCode"]) { return UITextContentTypeOneTimeCode; + } } return hints[0]; @@ -285,7 +309,7 @@ - (void)setTextInputClient:(int)client { } - (void)setAutofillId:(NSString*)autofillId { - _autofillId = [autofillId copy]; + _autofillId = [[autofillId copy] autorelease]; } - (void)setTextInputState:(NSDictionary*)state { @@ -873,23 +897,26 @@ - (void)setTextInputClient:(int)client withConfiguration:(NSDictionary*)configur } } else { NSAssert(clientUniqueId != nil, @"The client's unique id can't be null"); - for (FlutterTextInputView* v in _inputViews) + for (FlutterTextInputView* v in _inputViews) { [v removeFromSuperview]; - for (UIView* subview in [_inputHider subviews]) + } + for (UIView* subview in [_inputHider subviews]) { [subview removeFromSuperview]; + } [_inputViews removeAllObjects]; for (NSDictionary* field in fields) { - FlutterTextInputView* newInputView = [[FlutterTextInputView alloc] init]; + FlutterTextInputView* newInputView = [[[FlutterTextInputView alloc] init] autorelease]; newInputView.textInputDelegate = _textInputDelegate; [_inputViews addObject:newInputView]; NSString* autofillId = _uniqueIdFromDictionary(field); [newInputView setAutofillId:autofillId]; - if ([clientUniqueId isEqualToString:autofillId]) + if ([clientUniqueId isEqualToString:autofillId]) { _activeView = newInputView; + } [FlutterTextInputPlugin setupInputView:newInputView WithConfiguration:field]; [_inputHider addSubview:newInputView]; From bec64d363fdaeae36664896073a295b183a317b3 Mon Sep 17 00:00:00 2001 From: LongCat is Looong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Mon, 13 Apr 2020 13:18:46 -0700 Subject: [PATCH 08/10] remove FLUTTER_EXPORT --- .../darwin/ios/framework/Source/FlutterTextInputPlugin.h | 6 ------ .../darwin/ios/framework/Source/FlutterTextInputPlugin.mm | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 3c4f89d3d8011..c5571ee10e0b3 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -10,9 +10,6 @@ #include "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" #include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h" -#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG -FLUTTER_EXPORT -#endif @interface FlutterTextInputPlugin : NSObject @property(nonatomic, assign) id textInputDelegate; @@ -39,9 +36,6 @@ FLUTTER_EXPORT @end /** A range of text in the buffer of a Flutter text editing widget. */ -#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG -FLUTTER_EXPORT -#endif @interface FlutterTextRange : UITextRange @property(nonatomic, readonly) NSRange range; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index c07c3b4a3fa31..a0af075bdf5fd 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -89,8 +89,9 @@ static UIReturnKeyType ToUIReturnKeyType(NSString* inputType) { } static UITextContentType ToUITextContentType(NSArray* hints) { - if (hints == nil || hints.count == 0) + if (hints == nil || hints.count == 0) { return @""; + } NSString* hint = hints[0]; if (@available(iOS 10.0, *)) { From 57bf5a5bd3f6c083df24571b5150d7b10b1706d9 Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Tue, 14 Apr 2020 10:16:39 -0700 Subject: [PATCH 09/10] added some objc memory management and style tweaks --- .../Source/FlutterTextInputPlugin.mm | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index a0af075bdf5fd..9e69645b12c4f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -199,7 +199,7 @@ static UITextContentType ToUITextContentType(NSArray* hints) { return hints[0]; } -static NSString* _uniqueIdFromDictionary(NSDictionary* dictionary) { +static NSString* uniqueIdFromDictionary(NSDictionary* dictionary) { NSDictionary* autofill = dictionary[@"autofill"]; return autofill == nil ? nil : autofill[@"uniqueIdentifier"]; } @@ -256,8 +256,11 @@ - (id)copyWithZone:(NSZone*)zone { @end +@interface FlutterTextInputView () +@property(nonatomic, copy) NSString* autofillId; +@end + @implementation FlutterTextInputView { - NSString* _autofillId; int _textInputClient; const char* _selectionAffinity; FlutterTextRange* _selectedTextRange; @@ -309,10 +312,6 @@ - (void)setTextInputClient:(int)client { _textInputClient = client; } -- (void)setAutofillId:(NSString*)autofillId { - _autofillId = [[autofillId copy] autorelease]; -} - - (void)setTextInputState:(NSDictionary*)state { NSString* newText = state[@"text"]; BOOL textChanged = ![self.text isEqualToString:newText]; @@ -736,10 +735,11 @@ - (void)updateEditingState { @"text" : [NSString stringWithString:self.text], }; - if (_textInputClient == 0 && _autofillId != nil) + if (_textInputClient == 0 && _autofillId != nil) { [_textInputDelegate updateEditingClient:_textInputClient withState:state withTag:_autofillId]; - else + } else { [_textInputDelegate updateEditingClient:_textInputClient withState:state]; + } } - (BOOL)hasText { @@ -801,14 +801,15 @@ - (BOOL)accessibilityElementsHidden { @end -@implementation FlutterTextInputPlugin { - FlutterTextInputView* _nonAutofillInputView; - FlutterTextInputView* _nonAutofillSecureInputView; +@interface FlutterTextInputPlugin () +@property (nonatomic, retain) FlutterTextInputView* nonAutofillInputView; +@property (nonatomic, retain) FlutterTextInputView* nonAutofillSecureInputView; +@property (nonatomic, retain) NSMutableArray* inputViews; +@property (nonatomic, assign) FlutterTextInputView* activeView; +@property (nonatomic, retain) FlutterTextInputViewAccessibilityHider* inputHider; +@end - NSMutableArray* _inputViews; - FlutterTextInputView* _activeView; - FlutterTextInputViewAccessibilityHider* _inputHider; -} +@implementation FlutterTextInputPlugin @synthesize textInputDelegate = _textInputDelegate; @@ -886,22 +887,22 @@ - (void)hideTextInput { - (void)setTextInputClient:(int)client withConfiguration:(NSDictionary*)configuration { NSArray* fields = configuration[@"fields"]; - NSString* clientUniqueId = _uniqueIdFromDictionary(configuration); + NSString* clientUniqueId = uniqueIdFromDictionary(configuration); bool isSecureTextEntry = [configuration[@"obscureText"] boolValue]; if (fields == nil) { _activeView = isSecureTextEntry ? _nonAutofillSecureInputView : _nonAutofillInputView; - [FlutterTextInputPlugin setupInputView:_activeView WithConfiguration:configuration]; + [FlutterTextInputPlugin setupInputView:_activeView withConfiguration:configuration]; if (![_activeView isDescendantOfView:_inputHider]) { [_inputHider addSubview:_activeView]; } } else { NSAssert(clientUniqueId != nil, @"The client's unique id can't be null"); - for (FlutterTextInputView* v in _inputViews) { - [v removeFromSuperview]; + for (FlutterTextInputView* view in _inputViews) { + [view removeFromSuperview]; } - for (UIView* subview in [_inputHider subviews]) { + for (UIView* subview in _inputHider.subviews) { [subview removeFromSuperview]; } @@ -912,14 +913,14 @@ - (void)setTextInputClient:(int)client withConfiguration:(NSDictionary*)configur newInputView.textInputDelegate = _textInputDelegate; [_inputViews addObject:newInputView]; - NSString* autofillId = _uniqueIdFromDictionary(field); - [newInputView setAutofillId:autofillId]; + NSString* autofillId = uniqueIdFromDictionary(field); + newInputView.autofillId = autofillId; if ([clientUniqueId isEqualToString:autofillId]) { _activeView = newInputView; } - [FlutterTextInputPlugin setupInputView:newInputView WithConfiguration:field]; + [FlutterTextInputPlugin setupInputView:newInputView withConfiguration:field]; [_inputHider addSubview:newInputView]; } } @@ -929,7 +930,7 @@ - (void)setTextInputClient:(int)client withConfiguration:(NSDictionary*)configur } + (void)setupInputView:(FlutterTextInputView*)inputView - WithConfiguration:(NSDictionary*)configuration { + withConfiguration:(NSDictionary*)configuration { NSDictionary* inputType = configuration[@"inputType"]; NSString* keyboardAppearance = configuration[@"keyboardAppearance"]; NSDictionary* autofill = configuration[@"autofill"]; From 7ea67497ed1a7a58d5f351a9371cd174a3886c06 Mon Sep 17 00:00:00 2001 From: LongCat is Looong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Tue, 14 Apr 2020 10:54:00 -0700 Subject: [PATCH 10/10] run formatter --- .../ios/framework/Source/FlutterTextInputPlugin.mm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 9e69645b12c4f..068b8bcb76622 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -802,11 +802,11 @@ - (BOOL)accessibilityElementsHidden { @end @interface FlutterTextInputPlugin () -@property (nonatomic, retain) FlutterTextInputView* nonAutofillInputView; -@property (nonatomic, retain) FlutterTextInputView* nonAutofillSecureInputView; -@property (nonatomic, retain) NSMutableArray* inputViews; -@property (nonatomic, assign) FlutterTextInputView* activeView; -@property (nonatomic, retain) FlutterTextInputViewAccessibilityHider* inputHider; +@property(nonatomic, retain) FlutterTextInputView* nonAutofillInputView; +@property(nonatomic, retain) FlutterTextInputView* nonAutofillSecureInputView; +@property(nonatomic, retain) NSMutableArray* inputViews; +@property(nonatomic, assign) FlutterTextInputView* activeView; +@property(nonatomic, retain) FlutterTextInputViewAccessibilityHider* inputHider; @end @implementation FlutterTextInputPlugin