一个最简单的弹出菜单是这个样子
首先新建一个macOS工程,一切默认即可。
填写项目名称和其他信息。
首先清理SwiftUI代码。只保留程序入口即可。
打开 AppDelegate.swift 在 AppDelegate 这个类的类变量中声明menu。如果不在类变量中声明,状态栏的图标会闪一下就消失。
let statusItem = NSStatusBar.system.statusItem(withLength:NSStatusItem.squareLength)
然后在 applicationDidFinishLaunching 这方法中添加以下代码。
func applicationDidFinishLaunching(_ aNotification: Notification) {
if let button = statusItem.button {
button.image = NSImage(named:NSImage.Name("StatusBarIcons"))
}
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Print", action: #selector(AppDelegate.printString(_:)), keyEquivalent: "P"))
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
statusItem.menu = menu
}
@objc func printString(_ sender: Any?) {
print("Hello MacOS")
}
添加状态栏图标到 Assets.xcassets 中,图标尺寸3232即可,我用的4848,渲染方式(Render As)选Templete Image:
接下来隐藏dock中的图标。打开Info.list,添加一行
<key>LSUIElement</key>
<true/>
或者添加一行 Application is agent (UIElement) 设置为YES
然后Run,大功告成。文章开始那个简单的效果已经实现了,但是还没有完,接下来我们要把一个窗口显示在状态栏哪里。如下面:
这个窗口用到了ViewController,把Statusbar和ViewController结合起来,写一个简单的页面。
首先新建一个Cocoa Class ,我们命名为PopMenuViewController,继承NSViewController,不用创建XIB。
然后打开Main.storyboard,新建ViewControllerSence。点击Xcode右上角的 + 号。然后选择ViewController
关联刚才新建的ViewController的swift类。
接下来开始编码:
在 AppDelegate.swift 中定义类变量。
let popover = NSPopover()
在刚才新建的PopMenuViewController.swift中新建一个静态的初始化方法,便于使用这个Controller。
import Cocoa
class PopMenuViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
}
extension PopMenuViewController {
static func initController() -> PopMenuViewController {
let storyboard = NSStoryboard(name: NSStoryboard.Name( "Main"), bundle: nil)
let identifier = NSStoryboard.SceneIdentifier("PopMenuViewController")
guard let viewcontroller = storyboard.instantiateController(withIdentifier: identifier) as? PopMenuViewController else {
fatalError("Cannot find PopMenuViewController")
}
return viewcontroller
}
}
然后在AppDelegate.swift中关联这个类。
let popover = NSPopover()
func applicationDidFinishLaunching(_ aNotification: Notification) {
if let button = statusItem.button {
button.image = NSImage(named:NSImage.Name("StatusBarIcons"))
}
popover.contentViewController = PopMenuViewController.initController()
}
@objc func printString(_ sender: Any?) {
print("Hello MacOS")
}
然后把点击事件完成,AppDelegate.swift完整代码如下:
import Cocoa
import SwiftUI
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
let statusItem = NSStatusBar.system.statusItem(withLength:NSStatusItem.squareLength)
let popover = NSPopover()
func applicationDidFinishLaunching(_ aNotification: Notification) {
if let button = statusItem.button {
button.image = NSImage(named:NSImage.Name("StatusBarIcons"))
button.action = #selector(togglePopover(_:))
}
popover.contentViewController = PopMenuViewController.initController()
}
@objc func printString(_ sender: Any?) {
print("Hello MacOS")
}
@objc func togglePopover(_ sender: Any?) {
if popover.isShown {
closePopover(sender: sender)
} else {
showPopover(sender: sender)
}
}
func showPopover(sender: Any?) {
if let button = statusItem.button {
popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}
func closePopover(sender: Any?) {
popover.performClose(sender)
}
}
现在Run一下,看看成果吧。
接下来可以随便在PopMenuViewController中添加自己的布局,做成你想要的样子。
现在还是有个问题,就是当鼠标点击其他区域的时候,窗体并不会自动消失,解决思路就是在当鼠标点击的时候接受事件,关闭窗体。
在弹出窗体的地方添加注册事件监听代码:
monitor = NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDown,.rightMouseDown] ){ [weak self] event in
if let strongSelf = self, strongSelf.popover.isShown {
strongSelf.closePopover(sender: event)
}
}
在窗体消失的时候取消注册:
if monitor != nil {
NSEvent.removeMonitor(monitor!)
}
monitor = nil
现在Run一下,看看成果吧,现在无论鼠标在屏幕的其他任何地方点击,窗体都会消失了。