Skip to content

Symmetric keyboard dismissal with keep-focus-on-submit as the escape hatch - #353

Open
gwleuverink wants to merge 1 commit into
NativePHP:mainfrom
trailhead-labs:fix/335-keyboard-dismiss-symmetry
Open

Symmetric keyboard dismissal with keep-focus-on-submit as the escape hatch#353
gwleuverink wants to merge 1 commit into
NativePHP:mainfrom
trailhead-labs:fix/335-keyboard-dismiss-symmetry

Conversation

@gwleuverink

Copy link
Copy Markdown
Contributor

Fixes #335. Companion PR: NativePHP/mobile-ui#53 carries the input-renderer half. They need to land together.

The report was a chat composer, a bare-text-input and a send pressable in one row. On iOS, tapping send fires @press and dismisses the keyboard in the same tap, so the user re-focuses the field after every message. keep-focus-on-submit didn't help, it only covered the return key. Reproduced exactly as filed.

While verifying I mapped the dismissal behaviour on both platforms, and they disagreed on nearly everything. Tapping a button while a field was focused dismissed on iOS but not on Android. Submit dismissed on iOS but not on Android, even though the docs describe dismiss as the default. And keep-focus-on-submit worked on iOS but was silently ignored on Android, the renderer never parsed the prop.

The fix rests on three decisions. The platforms should behave identically, and the web-like default wins: the keyboard drops on submit and on any tap outside the field, which is also the right default for regular forms, where tapping Submit should close the keyboard. Only Android's defaults change, iOS already behaved this way and its defaults are untouched. And keep-focus-on-submit becomes the one escape hatch that genuinely works on both platforms, keeping the field focused through the return key and through button sends alike. Tapping empty space always dismisses, even with the attribute, same as iMessage and WhatsApp transcripts.

Mechanically, the screen-level dismiss gesture is now a regular gesture instead of a simultaneousGesture, so it only fires for taps nothing else claimed, the plain-area path. Taps on interactive elements dismiss through a small KeyboardFocusPolicy that press dispatch consults, which is what lets the focused field's attribute exempt them. Android mirrors the same shape, its existing root clickable stays the plain-area path and the press modifiers route through the policy.

While testing on iOS we also caught a subtle one, worth spelling out because it shaped how press dispatch works. iOS commits a pending autocorrect suggestion when you tap elsewhere, and with the keyboard no longer resigning on a send tap, that commit now lands after the press has already been dispatched. The sequence was: tap send, PHP receives the press and sends the draft as typed, iOS then applies the correction to the field, and the late change event re-fills the server-side draft that send() just cleared. The field stays filled with the corrected word, and tapping send again produces a ghost second message.

Press dispatch on iOS now does two things about that. It flushes the focused field's pending change first, and it defers the press event by one runloop turn, so the correction's change event reaches PHP before the press does. A send with a pending suggestion now sends the corrected text, clears the field, and adds exactly one message. Verified on the simulator. Android turned out not to have the race at all, Gboard only suggests passively and never applies a correction on an unrelated tap, also verified on the emulator.

The behavioural change is Android-only and lives mostly in the companion PR: return and button taps start dropping the keyboard by default there, like iOS always did. No API changes, existing code runs as is. Android chat screens that relied on the keyboard staying up add keep-focus-on-submit to the input.

Verified on device, Android emulator (dumpsys mInputShown plus screenshots) and iOS simulator (manual):

iOS before iOS after Android before Android after
Button tap, default drops drops keeps drops
Submit, default drops drops keeps drops
Plain-area tap, default drops drops drops drops
Button tap + attr drops keeps keeps keeps
Submit + attr keeps keeps keeps keeps
Plain-area tap + attr drops drops drops drops

Also verified on both platforms: the multiline composer from the issue (return inserts a newline, the send button sends the whole draft, the keyboard never moves) and the autocorrect scenario above.

One known limitation, pre-existing: with the attribute on a single-line field, the return key still shows a brief keyboard bounce on iOS (resign then refocus). That's an acknowledged SwiftUI platform bug, Apple's own focus sample reproduces it, and the in-code notes in NativeUITextInputCore already point at a UIKit-backed field as the eventual fix if it ever becomes worth it. It doesn't affect button sends or multiline composers, which is where the attribute matters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Masterclass] No way to keep the keyboard up when sending via a button - tap-to-dismiss fires on every tap outside the input

1 participant