在SwiftUI中,如何隐藏macOS的默认Command菜单?

6
我正在尝试将我的AppDelegate macOS应用程序切换到SwiftUI生命周期,但似乎找不到如何处理CommandMenu的方法。我只想删除这些默认菜单项(文件、编辑、查看等)。过去,我会从Storyboard中删除它们,但我这里没有使用Storyboard。在SwiftUI中有没有一种方法可以删除这些项目?
我想要删除的项目:

The items I want to delete

我知道如何通过以下方式添加新项目:

.commands {
  MyAppMenus()
}  

但这只是将它们与现有的菜单项内联添加。
5个回答

5

SwiftUI -- 使用自定义内容覆盖AppDelegate:

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

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" }
            }
        }
    }
}

结果:

enter image description here


这对我有用。我似乎没有像其他评论者提到的那样在SwiftUI中添加菜单项的问题,所以我能够将其放在applicationDidFinishLaunching下面。注意:applicationWillUpdate被频繁调用,永远不会停止。 - danneu
applicationDidFinishLaunching在菜单第一次刷新之前有效,这就是为令人满意的applicationWillUpdate - 在菜单刷新时再次更新。 - Andrew_STOP_RU_WAR_IN_UA

2

在SwiftUI添加更多关于菜单的支持之前,我认为你需要担心SwiftUI在更新window.body时重置NSApp.mainMenu。我没有尝试过所有调整mainMenu的方法,但是在我尝试的方法中,缺陷在于SwiftUI似乎没有检查它是否最后设置了NSApp.mainMenu或者是否有其他东西设置了它。

因此,无论你如何管理菜单,在SwiftUI更新之后进行更新。

使用KVO并监视NSApp的主菜单更改。然后使用xib进行更改,或重新设置整个菜单,或编辑SwiftUI的菜单。

示例:

@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 { 
        //... 
    }
}

这似乎在使用Swift 5针对macOS 12.3的Xcode 13.4.1中有效。

希望苹果很快添加更多控制选项。 Catalyst似乎有其他选项。 或者您可以创建传统的AppKit应用程序并将SwiftUI视图插入其中。


这似乎可靠。我在尝试将解决方案适应到一个 SwiftUI 文档型应用程序中,以删除附带的“文件>共享”菜单项时遇到了问题。SwiftUI 没有为共享命令提供 CommandGroupPlacement,因此我无法使用官方方式删除它。我可以通过 let fileMenu = items.first { $0.title == "File" } 获取文件菜单,并通过 fileMenu?.submenu?.items.first {$0.title == "Share"} 获取共享菜单命令。但是,我无法弄清楚如何使用 KVO 观察者来检测 SwiftUI 何时重新添加共享命令。 - Peter Nowell
我想不出一个简单的解决方案。也许你可以使用NotificationCenter.default.publisher(for: NSMenu.didAddItemNotification, object: nil)代替KVO,并且每次检查它是否是你想要删除的项目(例如“分享”)。然后在它是时将其删除。我发现在这一点上操纵菜单太麻烦了。 - waggles

0

您可以通过AppDelegate文件删除命令菜单项:

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

这个苹果开发者论坛的帖子也许会有所帮助:https://developer.apple.com/forums/thread/649096


5
如果我使用 Catalyst,那么这个方法就可行了,但是我没有使用Catalyst - UIMenuBuilder在macOS上不可用。 - JoeBayLD
@JoeBayLD 噢,对不起关于那个。 - Lemon

0
基于上述内容,但使用NotificationCenter而不是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()
    }

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

请问您能否详细阐述您的回答? - Luc-Olivier
3
你能否添加一些解释,说明这个方法是如何解决原帖作者的问题的。 - SiKing

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接