让人眼花缭乱的 macOS 菜单

这是 macOS 开发系列的第二篇文章——让人眼花缭乱的 macOS 菜单。

这有什么好说的,你不要骗我!手机上的菜单都是我用自定义视图撸出来的!

菜单的类型

在 macOS 开发中,所谓“菜单”并不只是一个自定义视图了(虽然自定义视图也可以实现),在 AppKit 里面名字直接叫“菜单”的类就占了两席之地,分别是 NSMenuNSMenuItem

在实际应用中,菜单对应了 5 种表现形式:

  • 应用的菜单栏,在屏幕的最上方

  • 弹出菜单,可以出现在当前窗口中的任何地方

  • 状态栏,从屏幕上方的菜单栏右边开始向左延伸

  • “上下文菜单”(Contextual Menus),点击右键或 “control + 左键” 触发

  • Dock 菜单,对程序坞(Dock)的应用图片点击右键或 “control + 左键” 触发

看着挺多,但是用起来倒是挺简单方便的。下面就把这几个新玩具都拉出来溜一溜~

应用菜单栏(Application Menu)

这个菜单栏在上一篇文章(不一样的 macOS Storyboard)里已经纠结过了。

概括一下:每个应用初始化的时候就自带了一个应用菜单栏,如果是使用 Storyboard 开发的项目,在 “Main.storyboard” 里面就可以直接对这个菜单栏进行各种各样功能上的调整了(也就限于逻辑,样子大概是改不动了,只能用系统控件…)

弹出菜单(Pop-up Menu)

这种菜单的含义比较宽泛,所有在当前窗口里面出现的、带有“弹出”感觉的菜单都可以属于这一类。从视觉上大致可以分成两种:对话框型 & 按钮型。

对话框型

这中文名是我自己取的…因为它长得像呀!它就是上面类型介绍里的图片所示的样子,对应的类是 NSPopover

这玩意儿在 iOS 9 和更老的版本中有类似的用法叫 UIPopoverController,在 iOS 9 之后就变成了 UIViewController 的一种展现方式了,具体参见 UIPopoverPresentationController

NSPopover 用起来跟上面说的几个类也是差不多的,只是它本身不是一个 ViewController,所以在展示之前需要先设置 contentViewController 以负责界面的显示。

更多风骚的用法还是参考官方文档为好 - NSPopover

按钮型

顾名思义,这是个看起来很像按钮的菜单,打开系统偏好设置 -> 通用,就能见到大把的按钮型弹出菜单,它们对应的类是 NSPopUpButton

在 xib 或者 storyboard 里面拖一个出来,能看到它跟普通按钮相比多出了这么一些独有的配置:

其中 “Type” 部分有两个可选值:Pop up & Pull Down,它们最直观的区别在于按钮后面跟着的蓝色部分,前者是上下箭头,后者则只有一个向下的箭头:

当然,它们在列表展开方式和使用场景上也是不一样的,想要追究其中细节的童鞋们,推荐一篇官方文档:Managing Pop-Up Buttons and Pull-Down Lists

状态栏(Status Bar)

作为一个中规中矩的菜单,状态栏菜单也是由两个部件组成的:NSStatusBar & NSStatusItem,从名字就能看出它们的关系了,具体用法也是看文档咯。

额外推荐一篇很详细的文章:Menus and Popovers in Menu Bar Apps for macOS | Ray Wenderlich

根据官方的说法,状态栏的位置稀缺,不保证你应用的菜单在上面一直是可用的,所以建议把它放在最后考虑(是的,甚至在 Dock Menu 之后)。做事比较克制的微信只把它用来显示未读消息条数,点击回调也只是打开微信主窗口而已。

而且苹果还建议我们提供一个隐藏的选项,在必要时给用户隐藏掉我们状态栏图标的机会。

Emmm….虽然苹果的话我们也不一定听就是了…

“上下文菜单”(Contextual Menus)

也就是俗称的右键菜单?

macOS 上还可以用 control + 左键触发

它跟应用菜单栏一样,是通过最常见的菜单样式来展现的,对应的类是: NSMenu & NSMenuItem

唯一不同的是,它不是通过点击一个什么按钮去触发的,而是通过重载 NSView 的 defaultMenu 属性来实现的,我们只需要定义好菜单的样子和内部逻辑,打开/收起菜单这样的琐事就交给系统去做好了。

在 xib 或 storyboard 里,把菜单链接到其他视图的 Outlets -> menu 上面,同样能实现右键触发的效果:

话说上图这个界面本身也是一个 Contextual Menu 呢。

Dock 菜单(Dock Menu)

阿哈!这也是个普普通通的 Menu,通过实现 NSDockTilePlugIn 这个协议里的 dockMenu() 方法就可以返回一个我们自定义的菜单啦。

我们还可以通过它来自定我们的应用图标在 Dock 上面的样子,比如加个角标或者改一下图标颜色什么的,不过在这样做之前,我们还需要看看这个类 NSDockTile。这就超纲了啊,不说了不说了。

总结

虽说自定义视图和显隐逻辑也可以实现菜单的功能,但是 AppKit 已经为我们封装了好几个类,让我们可以方便快捷地怼出一个功能丰富的应用了,它们是:

除了一些特殊的应用之外,我们的主要功能应该是在窗口里面提供的,而菜单通常都是些锦上添花的东西。不过在有余力的时候,为我们的用户增添一分“意外之喜”也是极好的吧。