SwiftUI: Changing default Command Menus on macOS

Viewed 1387

I'm trying to change the default commands in a macOS SwiftUI app. I would like to append some custom commands to the 'View' menu, but I can't get it to work.

This is what I tried:

@main
struct MyApp: App {

    var body: some Scene {
        
        WindowGroup {
            AppView()
                .environmentObject(AppModel.shared)
                .environment(\.managedObjectContext, AppModel.shared.cloudKitCoordinator.coreDataStack.viewContext)
        }
        .commands {
            ViewCommands()
            SidebarCommands()
            ElementCommands()
            NavigationCommands()
        }
        
    }
    
}

struct ViewCommands: Commands {

    var body: some Commands {
        
        CommandMenu("View") {
            Button("Do something View related") {}
        }
        
    }
    
}

But instead of appending the command to the 'View' menu, it creates a second menu with the same name:

enter image description here

Has anyone had luck changing the default command menu's or is this just a part of SwiftUI that's still a little raw?

3 Answers

Use CommandGroup, which has init options to append or replace existing menus:

.commands {
    CommandGroup(before: CommandGroupPlacement.newItem) {
        Button("before item") {
            print("before item")
        }
    }

    CommandGroup(replacing: CommandGroupPlacement.appInfo) {
        Button("Custom app info") {
            // show custom app info
        }
    }

    CommandGroup(after: CommandGroupPlacement.newItem) {
        Button("after item") {
            print("after item")
        }
    }
}

Nice tutorial: https://swiftwithmajid.com/2020/11/24/commands-in-swiftui/

The accepted answer is both generally correct in suggesting CommandGroup rather than CommandMenu and helpful in pointing us to Swift with Majid (a great site), but infuriatingly doesn't give us the exact answer - which CommandGroupPlacement for the View menu?

To save all others the trial and error it is

CommandGroup(before: CommandGroupPlacement.toolbar) {
    Toggle("Show X", isOn: $model.settings.showX)
    Toggle("Show Y", isOn: $model.settings.showY)
    Divider()
}

The only way that I have find for swiftUI to REMOVE default menu items is to do the following class:

class TheApp{
    static func removeUnnecessaryMenus() {
        if let menu = NSApplication.shared.mainMenu {
            menu.items.removeFirst{ $0.title == "Edit" }
            menu.items.removeFirst{ $0.title == "File" }
            menu.items.removeFirst{ $0.title == "Window" }
            menu.items.removeFirst{ $0.title == "View" }
        }
    }
}

and to call TheApp.removeUnnecessaryMenus() on some View init() func

This is BAD SOLUTION , but I have find no alternative for SwiftUI 2

Related