MacOS开发 - StatusBar状态栏弹出菜单

这篇博客详细介绍了如何使用SwiftUI和Cocoa在macOS上构建一个带有弹出菜单的状态栏应用。从新建工程到添加图标,再到实现窗口显示和隐藏,以及响应鼠标点击事件关闭窗口,一步步展示了完整的代码实现过程。最后,通过源代码链接提供了完整的项目参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一个最简单的弹出菜单是这个样子在这里插入图片描述
首先新建一个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一下,看看成果吧,现在无论鼠标在屏幕的其他任何地方点击,窗体都会消失了。

源代码:https://github.com/leconio/StatusbarPopMenu.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值