Using @FocusedBinding with a Class to update a CommandMenu

This is for MacOS. I am trying to send a message to a Model Object using a CommandMenu and then change the menu button title in response, with only partial success.

I am using @FocusedBinding and .focusedValue so that the model can be created in my view rather than at the App level. I want to do this so that I can switch to a document style app and have different model instances.

In the following example everything works as it should IF the model is a Struct. However I REQUIRE that the model be a Class so I can observe changes.

If the model is switched to a class by commenting out the struct Model and uncommenting the class Model in the example below there are two problems that I can't seem to fix:

First, the menu never switches from ‘Play’ to ‘Stop’

Second, I can’t change the Model var annotation to @StateObject - which is what a class really needs - without getting an error on the .focusedValue modifier.

Any help sorting this out would be greatly appreciated

import SwiftUI

@main
struct MessageApp : App {
    var body: some Scene {
        WindowGroup {
            MessageView()
        }
        .commands {
            AppCommands()
        }
    }
}


struct AppCommands : Commands {
    struct PlayMenu : View {
        @FocusedBinding(\.model) var model: Model?

        var body: some View {
            Button((model?.playing ?? false) ? 
                    "Stop" : "Play", action: { 
                     model?.send(command: .play)
             }).keyboardShortcut(.space, modifiers: [])
        }
    }

    var body: some Commands {
        CommandMenu("Play") {
            PlayMenu()
        }
    }
}


struct MessageView : View {
    @State private var model = Model()

    var body: some View {
        Rectangle()
            .focusedValue(\.model, $model)
            .frame(idealWidth: 600, idealHeight: 400)
    }
}

//class Model : ObservableObject{
//    @Published var playing: Bool = false
//
//    func send(command: ModelCommands) {
//        playing.toggle()
//        print(playing)
//    }
//}

struct Model{
    var playing: Bool = false

    mutating func send(command: ModelCommands) {
        playing.toggle()
        print(playing)
    }
}

struct FocusedModelBinding: FocusedValueKey {
    typealias Value = Binding<Model>
}

extension FocusedValues {
    var model: FocusedModelBinding.Value? {
        get { self[FocusedModelBinding.self] }
        set { self[FocusedModelBinding.self] = newValue }
    }
}

enum ModelCommands: CaseIterable{
    case play
}

I think what Apple would tell you to do would be to use the class-based Model like you gave in your example and rather than passing the model to your AppCommands, you would pass the playing property directly like: .focusedValue(\.playing, $model.playing) and your binding would become @FocusedBinding(\.playing) var playing: Bool?.

That being said, I think this solution is less than ideal and Apple should add a FocusedObject or similar property. Having to pass these properties directly involves a lot of code duplication and reduces functionality. Your example is straightforward, but in a more complex case Model may wish to react to, intercept, or otherwise want to capture the value change directly and doing it this way makes that much harder.

Using @FocusedBinding with a Class to update a CommandMenu
 
 
Q