在已经提到的三种方法中,设置 UserDefaults 可能是最干净的,但如前所述,它依赖于未记录的 UserDefaults 条目。
通过菜单中的索引位置(即最后一个位置)删除项目的方法依赖于它们将始终位于菜单末尾的假设。这可能是一个安全的假设,但 Apple 可以在未来做其他事情。
由于本地化原因,依赖菜单项名称也是有问题的。
最好使用选择器,如在 Objective-C 中所示,但它在 Swift 中不会那么简单,特别是对于“开始听写...”菜单项。您需要选择器的方法是startDictation(_:),但与在Objective-C 中不同的是,您不能像那样键入它。您需要指定它所属的@objc 类型。因此,只需搜索 Apple 的文档,对吗?祝你好运。它没有记录,并且该方法没有在 Swift 中公开。
我解决该问题的方法是在我的AppDelegate 中为该方法添加一个存根,然后使用它来获取选择器。你真的只需要一些 Swift 可以使用的类型来形成选择器。 But when the selector is tossed over the wall to the Obj-C side of Cocoa, that type just disappears.重要的是方法的名称、参数的数量、它们的顺序和名称。在这种情况下,与大多数操作方法一样,它需要一个参数(用于发送者):
@objc public func startDictation(_: Any) { }
AppDelegate 对于快速而肮脏的实现很方便,但在实际使用中,我更喜欢创建一个继承 NSObject 的类,并带有专门用于此类存根方法的 private 初始化程序。这样你就可以确保它们永远不会污染响应者链。基本上制作一个不可实例化的包,您可以使用这些方法来制作选择器。
现在我们需要一种方法来找到对应的菜单项,所以我在NSMenu上做了一个扩展
public extension NSMenu
{
func lastMenuItem(where condition: (NSMenuItem) -> Bool) -> NSMenuItem?
{
for item in items.reversed()
{
if let submenu = item.submenu
{
if let foundItem = submenu.lastMenuItem(where: condition) {
return foundItem
}
}
else if condition(item) { return item }
}
return nil
}
}
我选择反向搜索菜单,假设如果我决定在未来某个日期添加我自己的项目,它们很可能在我要删除的项目之前。然后在AppDelegate:
func removeUnwantedAutomaticMenus()
{
let unwantedActions: [Selector] =
[
#selector(AppDelegate.startDictation(_:)),
#selector(NSApplication.orderFrontCharacterPalette(_:)),
]
for action in unwantedActions {
NSApp.mainMenu?.lastMenuItem { $0.action == action }?.isHidden = true
}
}
如您所见,我选择隐藏项目而不是删除它们,但您当然可以删除它们。剩下的就是在AppDelegate.applicationDidFinishLaunching 中调用removeUnwantedAutomaticMenus()。
如果您以编程方式创建菜单,则可以通过使用NSMenuItem 中的tag 属性标记您添加的项目,然后检查该标签以确保您不会删除/隐藏这些项目,从而使其更加健壮自动添加的。
另一种方法是继承NSMenu,覆盖其addItem 和insertItem 方法以在添加NSMenuItem 之前检查它们的标签。简单地不要添加/插入任何带有不正确标签的NSMenuItems,Apple 自动插入的项目不会有。如果您将 Storyboards 用于您的菜单,这会很痛苦,因为您必须确保每个菜单的类都设置为您的自定义类,并且每个菜单项的标签设置正确。如果您以编程方式创建菜单,则确保一切设置正确会容易得多。