[iOS] Infer autocorrect value from autofillHints (#165637)

fix https://github.com/flutter/flutter/issues/134723

If autofillHints contains password-related hints, set autocorrect to
`false`.


https://github.com/user-attachments/assets/aeb95f74-cabc-40ff-8829-c49bce466a6d

I checked the behaviour in UIKit and it is necessary to set correction
and spell check to `No` if username or password is set to "Content
Type". The following video shows the combination of `TextField` and
option in UIKit.


https://github.com/user-attachments/assets/6c442a87-042a-4b34-b88f-7a438fefdbb3

code: https://github.com/koji-1009/pm_behavior_test/tree/native

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

---------

Co-authored-by: Loïc Sharma <737941+loic-sharma@users.noreply.github.com>
This commit is contained in:
Koji Wakamiya 2025-04-12 04:44:06 +09:00 committed by GitHub
parent 2a1b863663
commit 21018df029
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 130 additions and 11 deletions

View File

@ -274,7 +274,9 @@ class CupertinoSearchTextField extends StatefulWidget {
/// {@macro flutter.material.textfield.onTap}
final VoidCallback? onTap;
/// {@macro flutter.widgets.editableText.autocorrect}
/// Whether to enable autocorrection.
///
/// Defaults to true.
final bool autocorrect;
/// Whether to allow the platform to automatically format quotes.

View File

@ -400,7 +400,7 @@ class CupertinoTextField extends StatefulWidget {
this.autofocus = false,
this.obscuringCharacter = '•',
this.obscureText = false,
this.autocorrect = true,
this.autocorrect,
SmartDashesType? smartDashesType,
SmartQuotesType? smartQuotesType,
this.enableSuggestions = true,
@ -619,7 +619,7 @@ class CupertinoTextField extends StatefulWidget {
final bool obscureText;
/// {@macro flutter.widgets.editableText.autocorrect}
final bool autocorrect;
final bool? autocorrect;
/// {@macro flutter.services.TextInputConfiguration.smartDashesType}
final SmartDashesType smartDashesType;
@ -920,7 +920,7 @@ class CupertinoTextField extends StatefulWidget {
DiagnosticsProperty<String>('obscuringCharacter', obscuringCharacter, defaultValue: '•'),
);
properties.add(DiagnosticsProperty<bool>('obscureText', obscureText, defaultValue: false));
properties.add(DiagnosticsProperty<bool>('autocorrect', autocorrect, defaultValue: true));
properties.add(DiagnosticsProperty<bool>('autocorrect', autocorrect, defaultValue: null));
properties.add(
EnumProperty<SmartDashesType>(
'smartDashesType',

View File

@ -370,7 +370,9 @@ abstract class SearchDelegate<T> {
/// Defaults to the default value specified in [TextField].
final TextInputType? keyboardType;
/// {@macro flutter.widgets.editableText.autocorrect}
/// Whether to enable autocorrection.
///
/// Defaults to true.
final bool autocorrect;
/// {@macro flutter.services.TextInputConfiguration.enableSuggestions}

View File

@ -264,7 +264,7 @@ class TextField extends StatefulWidget {
this.statesController,
this.obscuringCharacter = '•',
this.obscureText = false,
this.autocorrect = true,
this.autocorrect,
SmartDashesType? smartDashesType,
SmartQuotesType? smartQuotesType,
this.enableSuggestions = true,
@ -485,7 +485,7 @@ class TextField extends StatefulWidget {
final bool obscureText;
/// {@macro flutter.widgets.editableText.autocorrect}
final bool autocorrect;
final bool? autocorrect;
/// {@macro flutter.services.TextInputConfiguration.smartDashesType}
final SmartDashesType smartDashesType;
@ -961,7 +961,7 @@ class TextField extends StatefulWidget {
DiagnosticsProperty<String>('obscuringCharacter', obscuringCharacter, defaultValue: '•'),
);
properties.add(DiagnosticsProperty<bool>('obscureText', obscureText, defaultValue: false));
properties.add(DiagnosticsProperty<bool>('autocorrect', autocorrect, defaultValue: true));
properties.add(DiagnosticsProperty<bool>('autocorrect', autocorrect, defaultValue: null));
properties.add(
EnumProperty<SmartDashesType>(
'smartDashesType',
@ -1695,6 +1695,7 @@ class _TextFieldState extends State<TextField>
dragStartBehavior: widget.dragStartBehavior,
scrollController: widget.scrollController,
scrollPhysics: widget.scrollPhysics,
autofillHints: widget.autofillHints,
autofillClient: this,
autocorrectionTextRectColor: autocorrectionTextRectColor,
clipBehavior: widget.clipBehavior,

View File

@ -826,7 +826,7 @@ class EditableText extends StatefulWidget {
this.readOnly = false,
this.obscuringCharacter = '•',
this.obscureText = false,
this.autocorrect = true,
bool? autocorrect,
SmartDashesType? smartDashesType,
SmartQuotesType? smartQuotesType,
this.enableSuggestions = true,
@ -908,6 +908,7 @@ class EditableText extends StatefulWidget {
this.magnifierConfiguration = TextMagnifierConfiguration.disabled,
this.undoController,
}) : assert(obscuringCharacter.length == 1),
autocorrect = autocorrect ?? _inferAutocorrect(autofillHints: autofillHints),
smartDashesType =
smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
smartQuotesType =
@ -1050,7 +1051,7 @@ class EditableText extends StatefulWidget {
/// {@template flutter.widgets.editableText.autocorrect}
/// Whether to enable autocorrection.
///
/// Defaults to true.
/// False on iOS if [autofillHints] contains password-related hints, otherwise true.
/// {@endtemplate}
final bool autocorrect;
@ -2108,6 +2109,38 @@ class EditableText extends StatefulWidget {
return resultButtonItem;
}
// Infer the value of autocorrect from autofillHints.
static bool _inferAutocorrect({required Iterable<String>? autofillHints}) {
if (autofillHints == null || autofillHints.isEmpty || kIsWeb) {
return true;
}
switch (defaultTargetPlatform) {
case TargetPlatform.iOS:
// username, password and newPassword are password related hint.
// newUsername is not supported on iOS.
final bool passwordRelatedHint = autofillHints.any(
(String hint) =>
hint == AutofillHints.username ||
hint == AutofillHints.password ||
hint == AutofillHints.newPassword,
);
if (passwordRelatedHint) {
// https://github.com/flutter/flutter/issues/134723
// Set autocorrect to false to prevent password bar from flashing.
return false;
}
case TargetPlatform.macOS:
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
break;
}
return true;
}
// Infer the keyboard type of an `EditableText` if it's not specified.
static TextInputType _inferKeyboardType({
required Iterable<String>? autofillHints,
@ -2260,7 +2293,7 @@ class EditableText extends StatefulWidget {
properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode));
properties.add(DiagnosticsProperty<bool>('obscureText', obscureText, defaultValue: false));
properties.add(DiagnosticsProperty<bool>('readOnly', readOnly, defaultValue: false));
properties.add(DiagnosticsProperty<bool>('autocorrect', autocorrect, defaultValue: true));
properties.add(DiagnosticsProperty<bool>('autocorrect', autocorrect, defaultValue: null));
properties.add(
EnumProperty<SmartDashesType>(
'smartDashesType',

View File

@ -3803,6 +3803,87 @@ void main() {
},
);
testWidgets(
'iOS autocorrect value is inferred from AutofillHints - username',
(WidgetTester tester) async {
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(),
child: Directionality(
textDirection: TextDirection.ltr,
child: EditableText(
controller: controller,
backgroundCursorColor: Colors.grey,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
autofillHints: const <String>[AutofillHints.username],
),
),
),
);
final EditableText editableText = tester.firstWidget(find.byType(EditableText));
expect(editableText.autocorrect, isFalse);
},
variant: const TargetPlatformVariant(<TargetPlatform>{TargetPlatform.iOS}),
skip: kIsWeb, // [intended]
);
testWidgets(
'iOS autocorrect value is inferred from AutofillHints - password',
(WidgetTester tester) async {
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(),
child: Directionality(
textDirection: TextDirection.ltr,
child: EditableText(
controller: controller,
backgroundCursorColor: Colors.grey,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
autofillHints: const <String>[AutofillHints.password],
),
),
),
);
final EditableText editableText = tester.firstWidget(find.byType(EditableText));
expect(editableText.autocorrect, isFalse);
},
variant: const TargetPlatformVariant(<TargetPlatform>{TargetPlatform.iOS}),
skip: kIsWeb, // [intended]
);
testWidgets(
'iOS autocorrect value is inferred from AutofillHints - newPassword',
(WidgetTester tester) async {
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(),
child: Directionality(
textDirection: TextDirection.ltr,
child: EditableText(
controller: controller,
backgroundCursorColor: Colors.grey,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
autofillHints: const <String>[AutofillHints.newPassword],
),
),
),
);
final EditableText editableText = tester.firstWidget(find.byType(EditableText));
expect(editableText.autocorrect, isFalse);
},
variant: const TargetPlatformVariant(<TargetPlatform>{TargetPlatform.iOS}),
skip: kIsWeb, // [intended]
);
testWidgets('Changing controller updates EditableText', (WidgetTester tester) async {
final TextEditingController controller1 = TextEditingController(text: 'Wibble');
addTearDown(controller1.dispose);