From a5dc884ec8b17f21b96fd376b48885653d50a37a Mon Sep 17 00:00:00 2001 From: Huan Lin Date: Mon, 23 May 2022 21:57:14 -0700 Subject: [PATCH 1/3] [platform_view]add focus support for platform view --- .../ios/framework/Source/FlutterEngine.mm | 31 ++++++++++++++++++ .../framework/Source/FlutterPlatformViews.mm | 23 +++++++++++++ .../Source/FlutterPlatformViewsTest.mm | 32 +++++++++++++++++++ .../Source/FlutterPlatformViews_Internal.h | 9 ++++++ .../Source/FlutterTextInputDelegate.h | 1 + .../Source/FlutterTextInputPlugin.mm | 22 +++++++++++++ .../Source/FlutterTextInputPluginTest.mm | 27 ++++++++++++++++ 7 files changed, 145 insertions(+) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index e70a8a235ef98..f04b78a549036 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -986,6 +986,37 @@ - (void)flutterTextInputView:(FlutterTextInputView*)textInputView arguments:@[ @(client) ]]; } +- (void)flutterTextInputViewDidResignFirstResponder:(FlutterTextInputView*)textInputView { + // Platform view's first responder detection logic + // + // All text input widgets (e.g. EditableText) are backed by a dummy UITextInput view + // in the text input plugin. When this dummy UITextInput view resigns first responder, + // check if any platform view becomes first responder. If any platform view becomes + // first responder, send a "viewFocused" channel message to inform the framework to un-focus + // the previously focused text input. + // + // Caveat: + // 1. This detection logic does not cover the scenario when a platform view becomes + // first responder without any flutter text input resigning its first responder status + // (e.g. user tapping on platform view first). For now it works fine because there can only be + // one first responder in iOS, so we do not need to keep platform view's first responder status + // in the text input plugin (which is different from Android implementation). + // + // 2. This detection logic assumes that all text input widgets are backed by a dummy + // UITextInput view in the text input plugin, which may not hold true in the future. + + // Have to check in the next run loop, because iOS requests the previous first responder to + // resign before requesting the next view to become first responder. + dispatch_async(dispatch_get_main_queue(), ^(void) { + long platform_view_id = self.platformViewsController->FindFirstResponderPlatformViewId(); + if (platform_view_id == -1) { + return; + } + + [_platformViewsChannel.get() invokeMethod:@"viewFocused" arguments:@(platform_view_id)]; + }); +} + #pragma mark - Undo Manager Delegate - (void)flutterUndoManagerPlugin:(FlutterUndoManagerPlugin*)undoManagerPlugin diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index a919ed8b75eee..507076e6378f1 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -19,6 +19,20 @@ #import "flutter/shell/platform/darwin/ios/ios_surface.h" #import "flutter/shell/platform/darwin/ios/ios_surface_gl.h" +@implementation UIView (FirstResponder) +- (BOOL)hasFirstResponderInViewHierarchySubtree { + if (self.isFirstResponder) { + return YES; + } + for (UIView* subview in self.subviews) { + if (subview.hasFirstResponderInViewHierarchySubtree) { + return YES; + } + } + return NO; +} +@end + namespace flutter { std::shared_ptr FlutterPlatformViewLayerPool::GetLayer( @@ -328,6 +342,15 @@ return [touch_interceptors_[view_id].get() embeddedView]; } +long FlutterPlatformViewsController::FindFirstResponderPlatformViewId() { + for (auto const& [id, root_view] : root_views_) { + if ([(UIView*)root_view.get() hasFirstResponderInViewHierarchySubtree]) { + return id; + } + } + return -1; +} + std::vector FlutterPlatformViewsController::GetCurrentCanvases() { std::vector canvases; for (size_t i = 0; i < composition_order_.size(); i++) { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index fba267419a116..9083ff356651d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -1105,4 +1105,36 @@ - (int)alphaOfPoint:(CGPoint)point onView:(UIView*)view { return pixel[3]; } +- (void)testHasFirstResponderInViewHierarchySubtree_viewItselfBecomesFirstResponder { + // For view to become the first responder, it must be a descendant of a UIWindow + UIWindow* window = [[UIWindow alloc] init]; + UITextField* textField = [[UITextField alloc] init]; + [window addSubview:textField]; + + [textField becomeFirstResponder]; + XCTAssertTrue(textField.isFirstResponder); + XCTAssertTrue(textField.hasFirstResponderInViewHierarchySubtree); + [textField resignFirstResponder]; + XCTAssertFalse(textField.isFirstResponder); + XCTAssertFalse(textField.hasFirstResponderInViewHierarchySubtree); +} + +- (void)testHasFirstResponderInViewHierarchySubtree_descendantViewBecomesFirstResponder { + // For view to become the first responder, it must be a descendant of a UIWindow + UIWindow* window = [[UIWindow alloc] init]; + UIView* view = [[UIView alloc] init]; + UIView* childView = [[UIView alloc] init]; + UITextField* textField = [[UITextField alloc] init]; + [window addSubview:view]; + [view addSubview:childView]; + [childView addSubview:textField]; + + [textField becomeFirstResponder]; + XCTAssertTrue(textField.isFirstResponder); + XCTAssertTrue(view.hasFirstResponderInViewHierarchySubtree); + [textField resignFirstResponder]; + XCTAssertFalse(textField.isFirstResponder); + XCTAssertFalse(view.hasFirstResponderInViewHierarchySubtree); +} + @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index 8d42bc879488d..d4d75b3268b17 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -177,6 +177,10 @@ class FlutterPlatformViewsController { void OnMethodCall(FlutterMethodCall* call, FlutterResult& result); + // Returns the platform view id if the platform view (or any of its descendant view) is the first + // responder. Returns -1 if no such platform view is found. + long FindFirstResponderPlatformViewId(); + private: static const size_t kMaxLayerAllocations = 2; @@ -329,4 +333,9 @@ class FlutterPlatformViewsController { - (UIView*)embeddedView; @end +@interface UIView (FirstResponder) +// Returns YES if a view or any of its descendant view is the first responder. Returns NO otherwise. +@property(nonatomic, readonly) BOOL hasFirstResponderInViewHierarchySubtree; +@end + #endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERPLATFORMVIEWS_INTERNAL_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h index 7b33539446d63..7ecda6a23bb93 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h @@ -59,6 +59,7 @@ typedef NS_ENUM(NSInteger, FlutterFloatingCursorDragState) { insertTextPlaceholderWithSize:(CGSize)size withClient:(int)client; - (void)flutterTextInputView:(FlutterTextInputView*)textInputView removeTextPlaceholder:(int)client; +- (void)flutterTextInputViewDidResignFirstResponder:(FlutterTextInputView*)textInputView; @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 799afd5bb7fbb..41454fa5007ca 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -39,6 +39,7 @@ static NSString* const kShowMethod = @"TextInput.show"; static NSString* const kHideMethod = @"TextInput.hide"; static NSString* const kSetClientMethod = @"TextInput.setClient"; +static NSString* const kSetPlatformViewClientMethod = @"TextInput.setPlatformViewClient"; static NSString* const kSetEditingStateMethod = @"TextInput.setEditingState"; static NSString* const kClearClientMethod = @"TextInput.clearClient"; static NSString* const kSetEditableSizeAndTransformMethod = @@ -1075,6 +1076,14 @@ - (BOOL)canBecomeFirstResponder { return _textInputClient != 0; } +- (BOOL)resignFirstResponder { + BOOL success = [super resignFirstResponder]; + if (success) { + [self.textInputDelegate flutterTextInputViewDidResignFirstResponder:self]; + } + return success; +} + - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { // When scribble is available, the FlutterTextInputView will display the native toolbar unless // these text editing actions are disabled. @@ -2071,6 +2080,9 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } else if ([method isEqualToString:kSetClientMethod]) { [self setTextInputClient:[args[0] intValue] withConfiguration:args[1]]; result(nil); + } else if ([method isEqualToString:kSetPlatformViewClientMethod]) { + [self setPlatformViewTextInputClient:[args[@"platformViewId"] longValue]]; + result(nil); } else if ([method isEqualToString:kSetEditingStateMethod]) { [self setTextInputEditingState:args]; result(nil); @@ -2187,6 +2199,16 @@ - (void)triggerAutofillSave:(BOOL)saveEntries { [self addToInputParentViewIfNeeded:_activeView]; } +- (void)setPlatformViewTextInputClient:(long)platformViewID { + // No need to track the platformViewID for now (unlike in Android), because in iOS there can + // only be one single first responder. When a platform view becomes first responder, hide + // this dummy text input view (`_activeView`) for the previously focused widget. + [self removeEnableFlutterTextInputViewAccessibilityTimer]; + _activeView.accessibilityEnabled = NO; + [_activeView removeFromSuperview]; + [_inputHider removeFromSuperview]; +} + - (void)setTextInputClient:(int)client withConfiguration:(NSDictionary*)configuration { [self resetAllClientIds]; // Hide all input views from autofill, only make those in the new configuration visible diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm index 065ae1bfaf339..2b562ecd5bf12 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm @@ -1866,4 +1866,31 @@ - (void)testFlutterTextInputPluginHostViewNotNil { XCTAssertNotNil([flutterEngine.textInputPlugin hostView]); } +- (void)testSetPlatformViewClient { + FlutterViewController* flutterViewController = [FlutterViewController new]; + FlutterTextInputPlugin* myInputPlugin = [[FlutterTextInputPlugin alloc] initWithDelegate:engine]; + myInputPlugin.viewController = flutterViewController; + + __weak UIView* activeView; + @autoreleasepool { + FlutterMethodCall* setClientCall = [FlutterMethodCall + methodCallWithMethodName:@"TextInput.setClient" + arguments:@[ + [NSNumber numberWithInt:123], self.mutablePasswordTemplateCopy + ]]; + [myInputPlugin handleMethodCall:setClientCall + result:^(id _Nullable result){ + }]; + activeView = myInputPlugin.textInputView; + XCTAssertNotNil(activeView.superview, @"activeView must be added to the view hierarchy."); + FlutterMethodCall* setPlatformViewClientCall = [FlutterMethodCall + methodCallWithMethodName:@"TextInput.setPlatformViewClient" + arguments:@{@"platformViewId" : [NSNumber numberWithLong:456]}]; + [myInputPlugin handleMethodCall:setPlatformViewClientCall + result:^(id _Nullable result){ + }]; + XCTAssertNil(activeView.superview, @"activeView must be removed from view hierarchy."); + } +} + @end From 760221482b87365c455d47b7123f871d7ebfc00d Mon Sep 17 00:00:00 2001 From: Huan Lin Date: Thu, 26 May 2022 11:24:50 -0700 Subject: [PATCH 2/3] nits --- .../ios/framework/Source/FlutterEngine.mm | 9 ++-- .../framework/Source/FlutterPlatformViews.mm | 6 +-- .../Source/FlutterPlatformViewsTest.mm | 8 ++-- .../Source/FlutterPlatformViews_Internal.h | 2 +- .../Source/FlutterTextInputPlugin.mm | 11 ++--- .../Source/FlutterTextInputPluginTest.mm | 45 +++++++++---------- 6 files changed, 38 insertions(+), 43 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index f04b78a549036..9ad3da40a398d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -990,7 +990,7 @@ - (void)flutterTextInputViewDidResignFirstResponder:(FlutterTextInputView*)textI // Platform view's first responder detection logic // // All text input widgets (e.g. EditableText) are backed by a dummy UITextInput view - // in the text input plugin. When this dummy UITextInput view resigns first responder, + // in the TextInputPlugin. When this dummy UITextInput view resigns first responder, // check if any platform view becomes first responder. If any platform view becomes // first responder, send a "viewFocused" channel message to inform the framework to un-focus // the previously focused text input. @@ -998,12 +998,11 @@ - (void)flutterTextInputViewDidResignFirstResponder:(FlutterTextInputView*)textI // Caveat: // 1. This detection logic does not cover the scenario when a platform view becomes // first responder without any flutter text input resigning its first responder status - // (e.g. user tapping on platform view first). For now it works fine because there can only be - // one first responder in iOS, so we do not need to keep platform view's first responder status - // in the text input plugin (which is different from Android implementation). + // (e.g. user tapping on platform view first). For now it works fine because the TextInputPlugin + // does not track the focused platform view id (which is different from Android implementation). // // 2. This detection logic assumes that all text input widgets are backed by a dummy - // UITextInput view in the text input plugin, which may not hold true in the future. + // UITextInput view in the TextInputPlugin, which may not hold true in the future. // Have to check in the next run loop, because iOS requests the previous first responder to // resign before requesting the next view to become first responder. diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 507076e6378f1..707d0733f49d0 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -20,12 +20,12 @@ #import "flutter/shell/platform/darwin/ios/ios_surface_gl.h" @implementation UIView (FirstResponder) -- (BOOL)hasFirstResponderInViewHierarchySubtree { +- (BOOL)flt_hasFirstResponderInViewHierarchySubtree { if (self.isFirstResponder) { return YES; } for (UIView* subview in self.subviews) { - if (subview.hasFirstResponderInViewHierarchySubtree) { + if (subview.flt_hasFirstResponderInViewHierarchySubtree) { return YES; } } @@ -344,7 +344,7 @@ - (BOOL)hasFirstResponderInViewHierarchySubtree { long FlutterPlatformViewsController::FindFirstResponderPlatformViewId() { for (auto const& [id, root_view] : root_views_) { - if ([(UIView*)root_view.get() hasFirstResponderInViewHierarchySubtree]) { + if ((UIView*)(root_view.get()).flt_hasFirstResponderInViewHierarchySubtree) { return id; } } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index 9083ff356651d..3ac57a2bf9e04 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -1113,10 +1113,10 @@ - (void)testHasFirstResponderInViewHierarchySubtree_viewItselfBecomesFirstRespon [textField becomeFirstResponder]; XCTAssertTrue(textField.isFirstResponder); - XCTAssertTrue(textField.hasFirstResponderInViewHierarchySubtree); + XCTAssertTrue(textField.flt_hasFirstResponderInViewHierarchySubtree); [textField resignFirstResponder]; XCTAssertFalse(textField.isFirstResponder); - XCTAssertFalse(textField.hasFirstResponderInViewHierarchySubtree); + XCTAssertFalse(textField.flt_hasFirstResponderInViewHierarchySubtree); } - (void)testHasFirstResponderInViewHierarchySubtree_descendantViewBecomesFirstResponder { @@ -1131,10 +1131,10 @@ - (void)testHasFirstResponderInViewHierarchySubtree_descendantViewBecomesFirstRe [textField becomeFirstResponder]; XCTAssertTrue(textField.isFirstResponder); - XCTAssertTrue(view.hasFirstResponderInViewHierarchySubtree); + XCTAssertTrue(view.flt_hasFirstResponderInViewHierarchySubtree); [textField resignFirstResponder]; XCTAssertFalse(textField.isFirstResponder); - XCTAssertFalse(view.hasFirstResponderInViewHierarchySubtree); + XCTAssertFalse(view.flt_hasFirstResponderInViewHierarchySubtree); } @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index d4d75b3268b17..8662e3b4f5c1d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -335,7 +335,7 @@ class FlutterPlatformViewsController { @interface UIView (FirstResponder) // Returns YES if a view or any of its descendant view is the first responder. Returns NO otherwise. -@property(nonatomic, readonly) BOOL hasFirstResponderInViewHierarchySubtree; +@property(nonatomic, readonly) BOOL flt_hasFirstResponderInViewHierarchySubtree; @end #endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERPLATFORMVIEWS_INTERNAL_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 41454fa5007ca..fd120998cd9c0 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -2081,7 +2081,8 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { [self setTextInputClient:[args[0] intValue] withConfiguration:args[1]]; result(nil); } else if ([method isEqualToString:kSetPlatformViewClientMethod]) { - [self setPlatformViewTextInputClient:[args[@"platformViewId"] longValue]]; + // This method call has a `platformViewId` argument, but we do not need it for iOS for now. + [self setPlatformViewTextInputClient]; result(nil); } else if ([method isEqualToString:kSetEditingStateMethod]) { [self setTextInputEditingState:args]; @@ -2199,10 +2200,10 @@ - (void)triggerAutofillSave:(BOOL)saveEntries { [self addToInputParentViewIfNeeded:_activeView]; } -- (void)setPlatformViewTextInputClient:(long)platformViewID { - // No need to track the platformViewID for now (unlike in Android), because in iOS there can - // only be one single first responder. When a platform view becomes first responder, hide - // this dummy text input view (`_activeView`) for the previously focused widget. +- (void)setPlatformViewTextInputClient { + // No need to track the platformViewID (unlike in Android). When a platform view + // becomes the first responder, simply hide this dummy text input view (`_activeView`) + // for the previously focused widget. [self removeEnableFlutterTextInputViewAccessibilityTimer]; _activeView.accessibilityEnabled = NO; [_activeView removeFromSuperview]; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm index 2b562ecd5bf12..bd656cb6cc3aa 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm @@ -88,7 +88,7 @@ - (void)setUp { textInputPlugin = [[FlutterTextInputPlugin alloc] initWithDelegate:engine]; - viewController = [FlutterViewController new]; + viewController = [[FlutterViewController alloc] init]; textInputPlugin.viewController = viewController; // Clear pasteboard between tests. @@ -167,7 +167,7 @@ - (FlutterTextRange*)getLineRangeFromTokenizer:(id)tokeniz #pragma mark - Tests - (void)testNoDanglingEnginePointer { __weak FlutterTextInputPlugin* weakFlutterTextInputPlugin; - FlutterViewController* flutterViewController = [FlutterViewController new]; + FlutterViewController* flutterViewController = [[FlutterViewController alloc] init]; __weak FlutterEngine* weakFlutterEngine; FlutterTextInputView* currentView; @@ -1825,7 +1825,7 @@ - (void)testFlutterTokenizerCanParseLines { } - (void)testFlutterTextInputPluginRetainsFlutterTextInputView { - FlutterViewController* flutterViewController = [FlutterViewController new]; + FlutterViewController* flutterViewController = [[FlutterViewController alloc] init]; FlutterTextInputPlugin* myInputPlugin = [[FlutterTextInputPlugin alloc] initWithDelegate:engine]; myInputPlugin.viewController = flutterViewController; @@ -1858,7 +1858,7 @@ - (void)testFlutterTextInputPluginHostViewNilCrash { } - (void)testFlutterTextInputPluginHostViewNotNil { - FlutterViewController* flutterViewController = [FlutterViewController new]; + FlutterViewController* flutterViewController = [[FlutterViewController alloc] init]; FlutterEngine* flutterEngine = [[FlutterEngine alloc] init]; [flutterEngine runWithEntrypoint:nil]; flutterEngine.viewController = flutterViewController; @@ -1867,30 +1867,25 @@ - (void)testFlutterTextInputPluginHostViewNotNil { } - (void)testSetPlatformViewClient { - FlutterViewController* flutterViewController = [FlutterViewController new]; + FlutterViewController* flutterViewController = [[FlutterViewController alloc] init]; FlutterTextInputPlugin* myInputPlugin = [[FlutterTextInputPlugin alloc] initWithDelegate:engine]; myInputPlugin.viewController = flutterViewController; - __weak UIView* activeView; - @autoreleasepool { - FlutterMethodCall* setClientCall = [FlutterMethodCall - methodCallWithMethodName:@"TextInput.setClient" - arguments:@[ - [NSNumber numberWithInt:123], self.mutablePasswordTemplateCopy - ]]; - [myInputPlugin handleMethodCall:setClientCall - result:^(id _Nullable result){ - }]; - activeView = myInputPlugin.textInputView; - XCTAssertNotNil(activeView.superview, @"activeView must be added to the view hierarchy."); - FlutterMethodCall* setPlatformViewClientCall = [FlutterMethodCall - methodCallWithMethodName:@"TextInput.setPlatformViewClient" - arguments:@{@"platformViewId" : [NSNumber numberWithLong:456]}]; - [myInputPlugin handleMethodCall:setPlatformViewClientCall - result:^(id _Nullable result){ - }]; - XCTAssertNil(activeView.superview, @"activeView must be removed from view hierarchy."); - } + FlutterMethodCall* setClientCall = [FlutterMethodCall + methodCallWithMethodName:@"TextInput.setClient" + arguments:@[ [NSNumber numberWithInt:123], self.mutablePasswordTemplateCopy ]]; + [myInputPlugin handleMethodCall:setClientCall + result:^(id _Nullable result){ + }]; + UIView* activeView = myInputPlugin.textInputView; + XCTAssertNotNil(activeView.superview, @"activeView must be added to the view hierarchy."); + FlutterMethodCall* setPlatformViewClientCall = [FlutterMethodCall + methodCallWithMethodName:@"TextInput.setPlatformViewClient" + arguments:@{@"platformViewId" : [NSNumber numberWithLong:456]}]; + [myInputPlugin handleMethodCall:setPlatformViewClientCall + result:^(id _Nullable result){ + }]; + XCTAssertNil(activeView.superview, @"activeView must be removed from view hierarchy."); } @end From 26d1faffabbed0a6a8ceb808f2df9166126d9a72 Mon Sep 17 00:00:00 2001 From: hellohuanlin <41930132+hellohuanlin@users.noreply.github.com> Date: Thu, 26 May 2022 14:38:18 -0700 Subject: [PATCH 3/3] Update shell/platform/darwin/ios/framework/Source/FlutterEngine.mm Co-authored-by: Chris Yang --- shell/platform/darwin/ios/framework/Source/FlutterEngine.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 9ad3da40a398d..cfc669c899b8d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -987,7 +987,7 @@ - (void)flutterTextInputView:(FlutterTextInputView*)textInputView } - (void)flutterTextInputViewDidResignFirstResponder:(FlutterTextInputView*)textInputView { - // Platform view's first responder detection logic + // Platform view's first responder detection logic: // // All text input widgets (e.g. EditableText) are backed by a dummy UITextInput view // in the TextInputPlugin. When this dummy UITextInput view resigns first responder,