Search code examples
macosswiftuimenuitem

Enabling and Disabling CommandGroup Menu Items


I have a very simple sample macOS application with one custom menu command just in order to test my ideas as follows.

import SwiftUI

@main
struct MenuMonsterMacApp: App {
    @State var fileOpenEnabled: Bool = true
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .frame(width: 480.0, height: 320.0)
        }.commands {
            CommandGroup(after: .newItem) {
                Button {
                    print("Open file, will you?")
                } label: {
                    Text("Open...")
                }
                .keyboardShortcut("O")
                .disabled(false)
            }
        }
    }
}

And I want to enable and disable this command with a click of a button that is placed in ContentView. So I've created an ObservableObject class to observe the boolean value of the File Open command as follows.

import SwiftUI

@main
struct MenuMonsterMacApp: App {
    @ObservedObject var menuObservable = MenuObservable()
    @State var fileOpenEnabled: Bool = true
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .frame(width: 480.0, height: 320.0)
        }.commands {
            CommandGroup(after: .newItem) {
                Button {
                    print("Open file, will you?")
                } label: {
                    Text("Open...")
                }
                .keyboardShortcut("O")
                .disabled(!fileOpenEnabled)
            }
        }.onChange(of: menuObservable.fileOpen) { newValue in
            fileOpenEnabled = newValue
        }
    }
}

class MenuObservable: ObservableObject {
    @Published var fileOpen: Bool = true
}

In my ContentView, which virtually runs the show, I have the following.

import SwiftUI

struct ContentView: View {
    @StateObject var menuObservable = MenuObservable()
    
    var body: some View {
        VStack {
            Button {
                menuObservable.fileOpen.toggle()
            } label: {
                Text("Click to disable 'File Open'")
            }
        }
    }
}

If I click on the button, the boolean status of the menu command in question won't change. Is this a wrong approach? If it is, how can enable and disable the menu command from ContentView? Thanks.


Solution

  • To enable and disable the command with a click of a button that is placed in ContentView, try the following approach, using passing environmentObject and a separate View for the menu Button.

    import SwiftUI
    
    @main
    struct MenuMonsterMacApp: App {
        @StateObject var menuObservable = MenuObservable()
    
        var body: some Scene {
            WindowGroup {
                ContentView().environmentObject(menuObservable)
                    .frame(width: 480.0, height: 320.0)
            }.commands {
                CommandGroup(after: .newItem) {
                    OpenCommand().environmentObject(menuObservable)
                }
            }
        }
      
    }
    
    struct OpenCommand: View {
        @EnvironmentObject var menuObservable: MenuObservable
        
        var body: some View {
            Button {
                print("Open file, will you?")
            } label: {
                Text("Open...")
            }
            .disabled(!menuObservable.fileOpen)
            .keyboardShortcut("O")
        }
    }
    
    class MenuObservable: ObservableObject {
        @Published var fileOpen: Bool = true
    }
    
    struct ContentView: View {
        @EnvironmentObject var menuObservable: MenuObservable
        
        var body: some View {
            VStack {
                Button {
                    menuObservable.fileOpen.toggle()
                } label: {
                    Text("Click to disable 'File Open'")
                }
            }
        }
    }