diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 9de83b2373972..9a54c84545822 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -1236,7 +1236,7 @@ @interface FlutterTextInputPlugin () // 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 @@ - (instancetype)init { _reusableInputView = [[FlutterTextInputView alloc] init]; _reusableInputView.secureTextEntry = NO; _autofillContext = [[NSMutableDictionary alloc] init]; - _activeView = _reusableInputView; + _activeView = [_reusableInputView retain]; _inputHider = [[FlutterTextInputViewAccessibilityHider alloc] init]; } @@ -1263,6 +1263,7 @@ - (instancetype)init { - (void)dealloc { [self hideTextInput]; [_reusableInputView release]; + [_activeView release]; [_inputHider release]; [_autofillContext release]; [super dealloc]; @@ -1386,19 +1387,19 @@ - (void)setTextInputClient:(int)client withConfiguration:(NSDictionary*)configur [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/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m index 4cbe318f791e1..4b0a0d4ce91cd 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m @@ -865,4 +865,31 @@ - (void)testFlutterTokenizerCanParseLines { 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