JoeBayLD
JoeBayLD

Reputation: 999

How do I hide default CommandMenu in SwiftUI on macOS?

I'm (attempting) switching over my AppDelegate macOS app to the SwiftUI lifecycle - but can't seem to find out how to handle the CommandMenu. I just want to delete these default menu items (Fie, Edit, View, etc...). In the past, I would just delete them from the Storyboard - but I'm not using a storyboard here. Is there a way to delete these items in SwiftUI?

The items I want to delete:

The items I want to delete

I know how to add new items via:

.commands {
  MyAppMenus()
}  

But that just adds them inline with the existing menu items.

Upvotes: 7

Views: 2704

Answers (5)

z2k
z2k

Reputation: 10410

Working off of the above, but using NotificationCenter vs KVO.

final class AppDelegate: NSObject, NSApplicationDelegate {
    func applicationDidFinishLaunching(_ notification: Notification) {
        let unwantedMenus = ["File", "Edit"]
        
        let removeMenus = {
            unwantedMenus.forEach {
                guard let menu = NSApp.mainMenu?.item(withTitle: $0) else { return }
                NSApp.mainMenu?.removeItem(menu)
            }
        }

        NotificationCenter.default.addObserver(
            forName: NSMenu.didAddItemNotification,
            object: nil,
            queue: .main
        ) { _ in
            // Must refresh after every time SwiftUI re adds
            removeMenus()
        }

        removeMenus()
    }
}

Upvotes: 3

Andrew
Andrew

Reputation: 11427

swiftUI -- override AppDelegate with your custom:

@main
struct PixieApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
///........
}

code of appDelegate:

final class AppDelegate: NSObject, NSApplicationDelegate {
    func applicationWillUpdate(_ notification: Notification) {
        DispatchQueue.main.async {
            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" }
            }
        }
    }
}

result:

enter image description here

As truly this is bad solution.

  1. will not work on non-English localizations
  2. can be flickering on menu line

But i didn't found better one. =(

Upvotes: 8

waggles
waggles

Reputation: 2854

Until SwiftUI adds more support for adjusting menus, I think you have to worry about SwiftUI reseting the NSApp.mainMenu whenever it updates a window.body. I haven't tried every method for adjusting the mainMenu, but of the methods I tried, the flaw was that SwiftUI seems to have no check for whether it last set NSApp.mainMenu or if something else did.

So however you are managing the menu, update it after SwiftUI has.

Use KVO and watch the NSApp for changes on .mainMenu. Then make your changes with a xib, or reseting the whole thing, or editing SwiftUI's menus.

Example:

@objc
class AppDelegate: NSObject, NSApplicationDelegate {
    
    var token: NSKeyValueObservation?
    
    func applicationDidFinishLaunching(_ notification: Notification) {
        
        // Adjust a menu initially
        if let m = NSApp.mainMenu?.item(withTitle: "Edit") {
            NSApp.mainMenu?.removeItem(m)
        }

        // Must refresh after every time SwiftUI re adds
        token = NSApp.observe(\.mainMenu, options: .new) { (app, change) in
            // Refresh your changes
            guard let menu = app.mainMenu?.item(withTitle: "Edit") else { return }
            app.mainMenu?.removeItem(menu)
        }
    }
}

struct MarblesApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some View { 
        //... 
    }
}

This seems to work in Xcode 13.4.1 with Swift 5 targeting macOS 12.3.

Hopefully Apple adds greater control soon. It seems Catalyst has other options. Or you can create a traditional AppKit app and insert the SwiftUI views into it.

Upvotes: 2

Qulou Wan
Qulou Wan

Reputation: 11

CommandGroup(replacing: CommandGroupPlacement.appVisibility, addition: {})

Upvotes: -3

Lemon
Lemon

Reputation: 1460

You can remove command menu items through the AppDelegate file:

override func buildMenu(with builder: UIMenuBuilder) {
    super.buildMenu(with: builder)
    builder.remove(menu: .services)
    builder.remove(menu: .format)
    builder.remove(menu: .toolbar)
}

This thread on the Apple Developer forum might help as well: https://developer.apple.com/forums/thread/649096

Upvotes: 0

Related Questions