fix(ui,ios): force TextInput.show on first tap (keyboard-up-on-first-tap)

User reported: 'input field still need to click twice' on iPhone.
The 020be77 Listener + requestFocus() approach was insufficient.

Root cause analysis:

  * Flutter's EditableText opens the platform TextInput method
    channel (which is what slides the iOS soft keyboard up) only
    after a TapGestureRecognizer wins the gesture arena.

  * Our outer Listener calls node.requestFocus() pre-arena. That
    flags the FocusNode as focused in Flutter's focus tree, but
    does NOT open the TextInput channel \u2014 so iOS keeps the OS
    keyboard hidden until EditableText's own tap recognizer wins
    on a second tap.

  * requestFocus on its own is therefore a no-op for the user
    visually: the cursor + caret appear briefly but the keyboard
    stays down.

Fix: in the Listener.onPointerDown handler, additionally invoke
'TextInput.show' on SystemChannels.textInput. This is the same
private platform RPC EditableText calls internally on attach;
forcing it ourselves slides the keyboard up regardless of arena
state.

Belt-and-braces with the existing requestFocus() guarantees
keyboard-on-first-tap on iPhone / iPad and is harmless on:
  * Android (the platform ignores the redundant show call when
    the keyboard is already up),
  * Linux / Windows / macOS desktop (no soft keyboard exists; the
    method channel handler returns success without doing
    anything).

No new imports needed \u2014 SystemChannels is already in
package:flutter/services.dart (imported for FilteringTextInputFormatter).

flutter build ios --release --no-codesign: 20.7 s, Runner.app
30.2 MB.
This commit is contained in:
EdisonJwa
2026-05-16 18:23:57 +08:00
parent b8e9c8549e
commit 23bd1c7930
+26
View File
@@ -1267,12 +1267,38 @@ class _ConnectFormState extends State<_ConnectForm> {
final FocusNode _passwordFocus = FocusNode(); final FocusNode _passwordFocus = FocusNode();
Widget _focusOnTap(Widget child, FocusNode node) { Widget _focusOnTap(Widget child, FocusNode node) {
// iOS first-tap-not-focusing workaround.
//
// Symptom: the user taps a TextField the first time after the
// app launches (or after the field has not held focus this
// session); the cursor doesn't appear and the on-screen keyboard
// doesn't slide up. A second tap then works normally.
//
// Root cause: Flutter's EditableText opens the platform
// TextInput channel (which is what actually slides the iOS
// keyboard up) only after a TapGestureRecognizer **wins** the
// gesture arena. Our outer Listener (HitTestBehavior.translucent,
// onPointerDown) used to call node.requestFocus() pre-arena,
// which marks the node focused in Flutter's tree but does not
// open the TextInput channel \u2014 so the OS keyboard stays hidden
// until the EditableText's own tap recognizer wins on a second
// tap.
//
// Fix: in addition to requestFocus(), invoke 'TextInput.show'
// on the SystemChannels.textInput method channel. This is the
// same private RPC EditableText uses internally; calling it
// ourselves forces the OS keyboard up regardless of arena state.
// Belt-and-braces with the Listener guarantees keyboard-on-first-
// tap on iPhone / iPad and is harmless on Android (the platform
// ignores the redundant show call when the keyboard is already
// up) and on desktop (no soft keyboard exists).
return Listener( return Listener(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onPointerDown: (_) { onPointerDown: (_) {
if (!node.hasFocus) { if (!node.hasFocus) {
node.requestFocus(); node.requestFocus();
} }
SystemChannels.textInput.invokeMethod<void>('TextInput.show');
}, },
child: child, child: child,
); );