@FocusedBinding

Hello,

During the talk there is a sample code using new property wrapper @FocusedBinding.

Can you expand more on this topic with small example how to use this new property wrapper, there has been clear examples of @AppStorage, @StateObject, @SceneStorage however no talk expands more on @FocusedBinding.

The documentation is not very clear neither.

Thank you.

Replies

FocusedBinding, along with the FocusedValue property wrapper, works in conjunction with the new Commands API to establish data dependencies in menu bar menus and key commands where the source of truth lives somewhere in the key/main window's focused view hierarchy. The resulting menu items and key commands are sensitive to the user's focus, and can be enabled, disabled, and customized accordingly.

The following example runs on macOS and iOS, but unfortunately some bugs in the seed prevent it from behaving as expected. On macOS, a custom menu bar menu exposes a command that is supposed to mutate state in the main window, but state-sharing between windows and the main menu is broken. On iOS, the same command is supposed to be exposed as a key command with a shortcut that appears in the keyboard shortcut discovery HUD, but in fact the Command and Keyboard Shortcut APIs are disabled on iOS. Support for all of this will come in a future seed.

Doing menus like this is a bit of a change from the old AppKit way—instead of searching the responder chain for a view that can validate the menu item and dynamically select an action, we arrange things so that the menu item can form a data dependency on some source of truth in the focused view hierarchy. The menu item uses the data to validate itself and perform its action.

Code Block
// This example runs on macOS, iOS, and iPadOS.
//
// Big Sur Seed 1 has some known issues that prevent state-sharing between
// windows and the main menu, so this example doesn't currently behave as
// expected on macOS. Additionally, the Commands API is disabled on iOS in Seed
// 1. These issues will be addressed in future seeds.
//
// The Focused Value API is available on all platforms. The Commands and
// Keyboard Shortcut APIs are available on macOS, iOS, iPadOS, and
// tvOS—everywhere keyboard input is accepted.
@main
struct MessageApp : App {
var body: some Scene {
WindowGroup {
MessageView()
}
.commands {
MessageCommands()
}
}
}
struct MessageCommands : Commands {
// Try to observe a binding to the key window's `Message` model.
//
// In order for this to work, a view in the key window's focused view
// hierarchy (often the root view) needs to publish a binding using the
// `View.focusedValue(_:_:)` view modifier and the same `\.message` key
// path (anologous to a key path for an `Environment` value, defined
// below).
@FocusedBinding(\.message) var message: Message?
// FocusedBinding is a binding-specific convenience to provide direct
// access to a wrapped value.
//
// `FocusedValue` is a more general form of the property wrapper, designed
// to work with all value types, including bindings. The following is
// functionally equivalent, but places the burden of unwrapping the bound
// value on the client.
// @FocusedValue(\.message) var message: Binding<Message>?
var body: some Commands {
CommandMenu("Message") {
Button("Send", action: { message?.send() })
.keyboardShortcut("D") // Shift-Command-D
.disabled(message?.text.isEmpty ?? true)
}
}
}
struct MessageView : View {
@State var message = Message(text: "Hello, SwiftUI!")
var body: some View {
TextEditor(text: $message.text)
.focusedValue(\.message, $message)
.frame(idealWidth: 600, idealHeight: 400)
}
}
struct Message {
var text: String
// ...
mutating func send() {
print("Sending message: \(text)")
// ...
}
}
struct FocusedMessageKey : FocusedValueKey {
typealias Value = Binding<Message>
}
extension FocusedValues {
var message: FocusedMessageKey.Value? {
get { self[FocusedMessageKey.self] }
set { self[FocusedMessageKey.self] = newValue }
}
}

How would one use this with a List's selection? I am having no luck.
To @lupinglade—there's a bug in the seed that prevents sharing state between windows and the main menu, so right now publishing a binding like in the example doesn't work. Something to fix in a future seed.
Thanks for reply and the explanation, this is really great, I hope the issues will be fixed to be able to port apps to use fully SwiftUI. Something like this implementation is really needed as the responder chain do not fit the SwiftUI. Thanks.
A work-around occurred to me that allows the code example in my other answer to behave as expected on macOS, but unfortunately there's no way for me to edit that answer. So, adding another answer.

The work-around is to read the focused binding from a View type instead of a Commands type, which we can do by defining a simple custom view to use as the CommandMenu content. Changing the definition of MessageCommands to something like the following gives you the expected behavior on macOS.

Code Block swift
struct MessageCommands : Commands {
    struct BodyView : View {
        @FocusedBinding(\.message) var message: Message?
        
        var body: some View {
            Button("Send", action: { message?.send() })
                .keyboardShortcut("D") // Shift-Command-D
                .disabled(message?.text.isEmpty ?? true)
        }
    }
    var body: some Commands {
        CommandMenu("Message") {
            BodyView()
        }
    }
}

I wonder how I would make it work when using an optional, like:

Code Block
struct FocusedDeckKey : FocusedValueKey {
    typealias Value = Binding<DeckViewModel?>
}


Because then my commands look like:

Code Block
struct AppCommands: Commands {
    @FocusedBinding(\.currentDeck) private var currentDeck: DeckViewModel?
}


👆💥 Type of expression is ambiguous without more context

In my case the value I am trying to use is an optional selection, so it's possible to be nil
"Something to fix in a future seed."

So this issue doesn't fall off the radar, especially w/ Xcode Beta 3 dropping today and seemingly not resolving the inability to properly set FocusedValue to pass data between views, is there any ETA as to if/when a fix might be coming?

For tvOS engineers, this presents a significant hurdle keeping us from using SwiftUI for anything substantial that we're hopeful will be remedied soon.

Thanks.
This appears to only work when a TextEditor or TextField is focused - am I missing a way to do this just based on the current window?
Thank you for great example!

Is this still broken? I can't seem to make it work.

What I'm trying to do is to provide a command to modify enum Binding that eventually should modify a Picker (a ToolbarItem) and another view. Though, while I can trigger a command action I can't see my binding ever assigned in the first place nor updated (testing on 11.0.1 (20B29)).
Any chance of this ("there's a bug in the seed that prevents sharing state between windows and the main menu") being resolved? As of macOS 11.1 beta 2, this still doesn't work.

I would love to see this get fixed to man!
I am running Xcode 12.3 and macOS 11.1. Is it expected that the above example should work? I am not having any luck and want to make sure I'm not missing something simple.