SwiftUI on macOS: Window Management for Commands

Hi,

I've been working quite a lot on a pretty big SwiftUI App, which will actively support multiple windows. I am looking to use the commands feature new in macOS 11, like this for example:

Code Block Swift
WindowGroup {
    WindowView()
}       
.commands {
  CommandMenu("Menu") {
        Button(action: {
print("test")
        }) {
            Text("Action")
        }
    }
}


Now the action obviously needs to be propagated. I would work with environment objects on every window here which basically hold a CommandState, specifying if a command is currently available and which action to take on it.

Every window would have one of those and when you switch windows the global state would update to be the one of the current window.

Now the question is: How do I do that? As far as I can see, there is no way in SwiftUI to know which window is in the foreground, there is a scenePhase environment variable, but it is active for both windows when I create two. There is also an isFocused environment variable, but in my testing it never changes and I also do not really get what it would do since, in my opinion, the documentation is not very clear on that.

Any options I haven't considered/other ideas?

Thanks in advance,
Jann
Did you resolve this? I have same question.
I also have the same question and no clue on how to resolve this thus far. The way I see this is that the viewmodel (when working with MVVM) needs to get instantiate below the definition of the WindowGroup, making it unavailable for the .commands modifier. What are we missing here?
Same question here. focusedValue and @FocusedBinding doesn't work if there are no text fields in the document, and mine doesn't have a text field despite being document-based.

1- Given a list of Sujet (Core Data managed objects for example, retrieved with @FetchRequest), in the list view :

    @State private var selectedSujet: Sujet?

    var body: some View {
        List(selection: $selectedSujet) {
            ForEach(sujets) { sujet in
               NavigationLink(destination: SujetDetail(sujet: sujet)) {
                  SujetListItem(sujet: sujet)
               }
           }
        }
         // focused values (Object, action, Set of selection, String, Bool, ...)

         // Passes the selected object in focusedSceneValue
         .focusedSceneValue(\.selectedSujet, $selectedSujet)
         // Passes the action to the "focus system", executed in this closure when command active in menu
         .focusedSceneValue(\.deleteSujetAction) {
             if let sujet = selectedSujet {
                print("delete: \(sujet.name)")
             }
         }
   }

2- Create an extension for FocusedValues for declarations

extension FocusedValues {
   // Binding of optional selected object
    var selectedSujet: Binding<Sujet?>? {
        get { self[SelectedSujetKey.self] }
        set { self[SelectedSujetKey.self] = newValue}
    }

   // Action declaration
    var deleteSujetAction: (() -> Void)? {
        get { self[DeleteSujetActionKey.self] }
        set { self[DeleteSujetActionKey.self] = newValue }
    }

    private struct SelectedSujetKey: FocusedValueKey {
        typealias Value = Binding<Sujet?>
    }

    private struct DeleteSujetActionKey: FocusedValueKey {
        typealias Value = () -> Void
    }

}

3- In MenuCommands :

struct SujetsMenuCommands: Commands {
   // Retrieve the focused values
    @FocusedValue(\.selectedSujet) var selectedSujet
    @FocusedValue(\.deleteSujetAction) var deleteSujetAction

    var body: some Commands {
        CommandMenu("Sujets") {
            Button { deleteSujetAction?() } label: { 
                  Text("Delete \(selectedSujetName)") 
            }.disabled(selectedSujet?.wrappedValue == nil || deleteSujetAction == nil )        
      }
   }

   // private var for comfort
    private var selectedSujetName: String {
        if let sujet = selectedSujet?.wrappedValue {
            return sujet.wrappedName
        } else {
            return "This Sujet"
        }
    }
}

If multiple selection:

   // 1- 
   @State private var selectedDonnees = Set<Donnee.ID>()

   .focusedSceneValue(\.selectedDonnees, $selectedDonnees)

   // 2- 
    var selectedDonnees: Binding<Set<Donnee.ID>>? {
        get { self[FocusedDonneesSelectionKey.self] }
        set { self[FocusedDonneesSelectionKey.self] = newValue }
    }

    private struct FocusedDonneesSelectionKey: FocusedValueKey {
        typealias Value = Binding<Set<Donnee.ID>>
    }

   // 3-
    @FocusedBinding(\.selectedDonnees) private var selectedDonnees: Set<Donnee.ID>?

In macOS, in multi-windows app, each with its own scene, MenuCommands act accordingly with only focused scene.

I don't have tested on iPadOS.

MacOS 12 - Xcode 13 beta 5

SwiftUI on macOS: Window Management for Commands
 
 
Q