SwiftUI, how to use Commands in a document based App

I have a SwiftUI document based app where multiple documents can be open at the same time. I now want to have menu items in the main menu that send commands to the active window. Additionally, I want menu items to be disabled if no matching object is selected in the active window.

As an example Finder can be used, where in the "Edit" menu the entries are different depending on the selected files.

I tried using a class as shown below to communicate between the views and the Commands structure, which basically works. Unfortunately, this causes a command to arrive at all open documents. And if I have an entry selected in one window but not in another, the AppState object only reflects the last selected state. If I change the window, the app does not notice this for the time being.

class AppState: ObservableObject
{
    static let shared = AppState()

    @Published var dirEntryCommand = PassthroughSubject<DirCommand,Never>()
    @Published var selectedDirEntry: DirectoryEntry.ID?
}

The Commands struct looks like this:

enum DirCommand: Int, Identifiable, CaseIterable
{
    case rename
    case moveUp
    case moveDown
    
    var id: RawValue { rawValue }
}

struct DirEntryCommands: Commands
{
    @ObservedObject var appState = AppState.shared
    
    var body: some Commands {
        CommandMenu("Directory Entry") {
            Section {
                Button("Move up") {
                    appState.dirEntryCommand.send(.moveUp)
                }
                .keyboardShortcut("u")
                Button("Move down") {
                    appState.dirEntryCommand.send(.moveDown)
                }
                .keyboardShortcut("d")
            }
            .disabled(appState.selectedDirEntry == nil)
            Section {
                Button("Rename…") {
                    appState.dirEntryCommand.send(.rename)
                }
                .keyboardShortcut("r")
            }
            .disabled(appState.selectedDirEntry == nil)
        }
    }
}

It is possible that I am taking the wrong approach and that there is another solution to this problem.

In a pure AppKit application I would implement the relevant commands in the ViewController and use the "magic" of the ResponderChain.

Replies

Look into focused values and focused bindings. That's how menus in SwiftUI apps access the document. The following articles may help you:

https://www.swiftdevjournal.com/accessing-the-document-in-a-swiftui-menu/

https://troz.net/post/2021/swiftui_mac_menus/

  • This looks promising. I'll give it a try. ;-)

Add a Comment