diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 9de83b23739..9a54c845458 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -1236,7 +1236,7 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) { // The current password-autofillable input fields that have yet to be saved. @property(nonatomic, readonly) NSMutableDictionary* autofillContext; -@property(nonatomic, assign) FlutterTextInputView* activeView; +@property(nonatomic, strong) FlutterTextInputView* activeView; @property(nonatomic, strong) FlutterTextInputViewAccessibilityHider* inputHider; @end @@ -1253,7 +1253,7 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) { _reusableInputView = [[FlutterTextInputView alloc] init]; _reusableInputView.secureTextEntry = NO; _autofillContext = [[NSMutableDictionary alloc] init]; - _activeView = _reusableInputView; + _activeView = [_reusableInputView retain]; _inputHider = [[FlutterTextInputViewAccessibilityHider alloc] init]; } @@ -1263,6 +1263,7 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) { - (void)dealloc { [self hideTextInput]; [_reusableInputView release]; + [_activeView release]; [_inputHider release]; [_autofillContext release]; [super dealloc]; @@ -1386,19 +1387,19 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) { [self changeInputViewsAutofillVisibility:NO]; switch (autofillTypeOf(configuration)) { case FlutterAutofillTypeNone: - _activeView = [self updateAndShowReusableInputView:configuration]; + self.activeView = [self updateAndShowReusableInputView:configuration]; break; case FlutterAutofillTypeRegular: // If the group does not involve password autofill, only install the // input view that's being focused. - _activeView = [self updateAndShowAutofillViews:nil - focusedField:configuration - isPasswordRelated:NO]; + self.activeView = [self updateAndShowAutofillViews:nil + focusedField:configuration + isPasswordRelated:NO]; break; case FlutterAutofillTypePassword: - _activeView = [self updateAndShowAutofillViews:configuration[kAssociatedAutofillFields] - focusedField:configuration - isPasswordRelated:YES]; + self.activeView = [self updateAndShowAutofillViews:configuration[kAssociatedAutofillFields] + focusedField:configuration + isPasswordRelated:YES]; break; } diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m index 4cbe318f791..4b0a0d4ce91 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m @@ -865,4 +865,31 @@ FLUTTER_ASSERT_ARC XCTAssertEqual(range.range.length, 20u); } +- (void)testFlutterTextInputPluginRetainsFlutterTextInputView { + FlutterTextInputPlugin* myInputPlugin; + id myEngine = OCMClassMock([FlutterEngine class]); + myInputPlugin = [[FlutterTextInputPlugin alloc] init]; + myInputPlugin.textInputDelegate = myEngine; + __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; + FlutterMethodCall* hideCall = [FlutterMethodCall methodCallWithMethodName:@"TextInput.hide" + arguments:@[]]; + [myInputPlugin handleMethodCall:hideCall + result:^(id _Nullable result){ + }]; + XCTAssertNotNil(activeView); + } + // This assert proves the myInputPlugin.textInputView is not deallocated. + XCTAssertNotNil(activeView); +} + @end