Fous, FocusState and Architecture

I am currently struggling with resolving what appear to be competing design issues, and (while I may be just demonstrating my own ignorance) I would like to share my thoughts in the hope that you may have useful insights.

For purposes of discussion, consider a large and complex data entry screen with multiple sections for input. For all of the usual reasons (such as reuse, performance management, etc) each of these sections is implemented as its own, separately-compiled View. The screen is, then, composed of a sequence of reusable components.

However, each of these components has internal structure and may contain multiple focusable elements (and internal use of .onKeyPress(.tab) {...} to navigate internally). And the logic of each component is such that it has an internal @FocusState variable defined with its own unique type.

So, obviously what I want is

  • on the one hand, to provide a tab-based navigation scheme for the screen as a whole, where focus moves smoothly from one component's internals to the next component, and
  • on the other hand ,to build components that don't know anything about each other and have no cross-component dependencies, so that they can be freely reused in different situations.

And that's where I'm stuck. Since focus state variables for different components can have different types, a single over-arching FocusState passed (as a binding) to each component doesn't seem possible or workable. But I don't know how else to approach this issue.

(Note: in UIKit, I've done things like this by direct manipulation of the Responder Chain, but I don't see how to apply this type of thinking to SwiftUI.)

Thoughts?

I’ve run into a similar issue in a macOS app and ended up rethinking how to use SwiftUI’s focus system.

The app has the usual triple-panel layout: sidebar, content, inspector. The content is two tables, either of which can be focused. Each table has a TableController, exposed via .focused(). The inspector reads the focused controller to update its display, and the sidebar shows its selection—both via @FocusedValue.

On paper this works, but there’s a catch: when the sidebar or inspector itself gains focus (e.g. a text field), the focused table controller becomes nil. I tried various .focus modifiers but could never get the behaviour I wanted.

What I really needed was a property influenced by focus but not identical to it. For example:

  • Use the focused table controller if one exists.
  • Otherwise fall back to the last focused table controller.
  • Add other rules as needed.

The solution was to introduce an active table controller managed by an @Observable object in the environment. The sidebar and inspector observe this active controller instead of the raw focused value. Whenever focus changes, the observable updates the active controller according to my rules. This also allows changing the active controller through other means (picker, button, etc.).

In your case, you might similarly combine focused values with some key state to derive an “active” entity. If focus remains your primary driver, you still get tab-navigation, keyboard control, and accessibility “for free.”

Finally, don’t forget about `.focused(_:equals:). If you need to drive focus from your controller (mapping active → focused), this modifier helps align SwiftUI focus with your active entity.

Hope that helps. Good luck!

Your post is extremely interesting and definitely requires more thought on my part. It seems that what you're doing is to build a FocusCoordinator, conceptually similar to a Navigation Coordinator (although I think that there may be some missing APIs preventing a comprehensive solution.

My immediate interest is on tab-navigation and I have some new information to offer about it. It turns out that in most cases, sending a <tab> to a focueed TextField will cause focus to change, BUT if the TextField is instantiated with the axis: AXIS parameter, the <tab> is added to the TextField body instead.

I don't know how Apple will classify it, but I'm calling this a bug and have reported it. (FB20505919).

Hi @richard_aurbach,

Thank you for your Feedback report. I've notified the SwiftUI team of the behavior differences you've identified and they are investigating improvements to the TextField API.

Please continue to use Feedback Assistant for status updates on your existing report:

Bug Reporting: What to expect after submission

https://developer.apple.com/bug-reporting/#after-submission

Cheers,

Paris X Pinkney |  WWDR | DTS Engineer

Fous, FocusState and Architecture
 
 
Q