Skip to content

Add @FocusState and .focused - #49

Merged
colemancda merged 5 commits into
masterfrom
feature/focus-state
Jul 23, 2026
Merged

Add @FocusState and .focused#49
colemancda merged 5 commits into
masterfrom
feature/focus-state

Conversation

@colemancda

Copy link
Copy Markdown
Member

Adds @FocusState and .focused(_:) / .focused(_:equals:), giving Swift two-way control of keyboard focus.

Shape

  • @FocusState stores its value in the same persisted StateBox @State uses, so it survives re-evaluation and participates in updates. Its $-projection is a distinct FocusState.Binding type, so only focus state can be bound to a field's focus. init() is available for Bool and optional values, matching SwiftUI.
  • .focused(_:) (Bool) and .focused(_:equals:) (optional Hashable, to arbitrate between fields) emit a focused modifier carrying whether this field currently holds focus plus a bool callback.
  • The interpreter attaches a FocusRequester to the field: a LaunchedEffect requests focus when Swift says it should hold it and clears focus when it shouldn't, while onFocusChanged reports the user's own focus moves back into the state.

The ordering subtlety

When focus moves from field A to B, both fire — A's blur and B's focus, in no guaranteed order. A naive blur handler clearing the state would wipe out the focus B just claimed. So the blur path only surrenders focus if the state still names that field. Covered by a dedicated test (a stale blur from the first field must not clear the second's focus), and confirmed live on-device.

Verification

  • swift test — two new tests (Bool round-trip; equals: arbitration incl. the stale-blur case), 71 total passing
  • Emulator (new "FocusState" catalog screen, two fields + focus buttons), all three paths checked against the real view hierarchy, not just the label:
    • Swift → field: "Focus email" set the label to Email and the second EditText (y 686–862) reported focused="true" — the correct field, not just any
    • field → Swift: tapping the Name field moved focus to the first EditText and updated the label to Name (the Email→Name handoff exercising the ordering guard)
    • clearing: "Dismiss keyboard" (focus = nil) left zero focused EditTexts and the label at none

Scope

Focus is wired into TextField/SecureField (the same node type). Other controls accept the modifier but don't yet act on it.

@colemancda
colemancda merged commit 1f889b5 into master Jul 23, 2026
6 checks passed
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.

1 participant