隐藏SwiftUI/MacOS应用程序的编辑菜单

3

我的MacOS应用程序没有任何文本编辑功能。我该如何隐藏自动添加到我的应用程序中的“编辑”菜单?我更喜欢在SwiftUI中完成这个操作。

我期望下面的代码应该可以工作,但实际上没有效果。

override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool { return false }
@main
struct MyApp: App {

var body: some Scene {
    WindowGroup {
        ContentView()
    }.commands {
        CommandGroup(replacing: .textEditing) {}
    }
}

}
5个回答

5
据我所知,您无法隐藏整个菜单,只能隐藏其中的元素组:
    .commands {
        CommandGroup(replacing: .pasteboard) { }
        CommandGroup(replacing: .undoRedo) { }
    }

文件菜单怎么样?根据苹果的说法,可以将其删除。https://developer.apple.com/design/human-interface-guidelines/macos/menus/menu-bar-menus/#file-menu - Tamas
1
通过 CommandGroup(replacing: .saveItem) { }CommandGroup(replacing: .newItem) { },您可以获得一个空的文件菜单。但是我没有看到完全摆脱它的方法。不幸的是,SwiftUI 在 macOS 上仍然缺乏很多... - ChrisR

3

当SwiftUI更新窗口的主体时,当前的建议对我失败了。

解决方案:

使用KVO并观察NSApp\.mainMenu上的更改。在SwiftUI完成后,您可以删除任何想要删除的内容。

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

        // Remove Multiple Menus
        ["Edit", "View", "Help", "Window"].forEach { name in
            NSApp.mainMenu?.item(withTitle: name).map { NSApp.mainMenu?.removeItem($0) }
        }
        
        

        // Must remove after every time SwiftUI re adds
        token = NSApp.observe(\.mainMenu, options: .new) { (app, change) in
            ["Edit", "View", "Help", "Window"].forEach { name in
                NSApp.mainMenu?.item(withTitle: name).map { NSApp.mainMenu?.removeItem($0) }
            }

            // Remove a single menu
            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 { 
        //... 
    }
}

思路:

无论是SwiftUI存在漏洞还是他们确实不希望您删除NSApp.mainMenu中的顶级菜单。目前,SwiftUI似乎重置了整个菜单,并且没有办法覆盖或自定义大多数详细信息(Xcode 13.4.1)。类似于CommandGroup(replacing: .textEditing) { }这样的命令不允许您删除或清除整个菜单。即使指定没有任何命令,分配新的NSApp.mainMenu也会在SwiftUI想要时被覆盖。

这似乎是一个非常脆弱的解决方案。应该有一种方法告诉SwiftUI不要触及NSApp.mainMenu,或者启用更多的自定义功能。否则,SwiftUI应该检查它是否拥有先前的菜单(菜单项为SwiftUI.AppKitMainMenuItem)。或者我错过了他们提供的一些工具。希望这在WWDC beta中得以解决?

(在针对macOS 12.3并排除Catalyst的情况下,使用Swift 5的Xcode 13.4.1)


2

对于那些正在寻找更新的人,请看一下我提出并回答的这个问题:

SwiftUI 更新 mainMenu [已解决] kludgey

我解决它的方法是将其放在AppDelegateapplicationWillUpdate函数中的DispatchQueue.main.async闭包中。

import Foundation
import AppKit

public class AppDelegate: NSObject, NSApplicationDelegate {
    public func applicationWillUpdate(_ notification: Notification) {
        DispatchQueue.main.async {
            let currentMainMenu = NSApplication.shared.mainMenu

            let editMenu: NSMenuItem? = currentMainMenu?.item(withTitle: "Edit")
            if nil != editMenu {
                NSApp.mainMenu?.removeItem(editMenu!)
            }
        }
    }
}

我花了整整4天的时间搜索和尝试各种方法 :) - 典型的情况是最终只需要改变2行代码


1

针对本地(Cocoa)应用程序

可以使用NSApplicationDelegate删除应用程序菜单。这种方法可能会在未来的macOS版本中失效(例如,如果编辑菜单的位置发生变化),但目前是有效的:

class MyAppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
  let indexOfEditMenu = 2
   
  func applicationDidFinishLaunching(_ : Notification) {
    NSApplication.shared.mainMenu?.removeItem(at: indexOfEditMenu)
  }
}


@main
struct MyApp: App {
  @NSApplicationDelegateAdaptor private var appDelegate: MyAppDelegate

  var body: some Scene {
    WindowGroup {
      ContentView()
    }.commands {
      // ...
    }
  }
}

针对Catalyst(UIKit)应用程序

对于基于Catalyst的macOS应用程序,方法与上面类似,只是使用从UIResponder派生的UIApplicationDelegate

class MyAppDelegate: UIResponder, UIApplicationDelegate, ObservableObject {
   override func buildMenu(with builder: UIMenuBuilder) {
      /// Only operate on the main menu bar.
      if builder.system == .main {
         builder.remove(menu: .edit)
      }
   }
}


@main
struct MyApp: App {
  @UIApplicationDelegateAdaptor private var appDelegate: MyAppDelegate

  var body: some Scene {
    WindowGroup {
      ContentView()
    }.commands {
      // ...
    }
  }
}

0

你应该能够通过稍微修改@waggles的答案来隐藏整个菜单。

class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {

    // SwiftUI updates menu on occlusion state change
    func applicationDidChangeOcclusionState(_ notification: Notification) {
        ["Edit", "View"].forEach { name in
            NSApp.mainMenu?.item(withTitle: name).map { NSApp.mainMenu?.removeItem($0) }
        }
    }
    
    func applicationDidFinishLaunching(_ notification: Notification) {
        if let app = notification.object as? NSApplication,  app.windows.count > 0 {
            // For some reason if window delegate is not set here, 
            // occulsionState notification fires with a delay on 
            // window unhide messing up the menu-update timing.
            app.windows.first?.delegate = self
        }
    }
}

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