macOS状态栏图标(系统右上角)NSStatusBar
macOS状态栏图标(系统右上角)NSStatusBar

macOS状态栏图标(系统右上角)NSStatusBar

NSStatusBar 是 macOS 中用于创建菜单栏图标(系统右上角)的 API,很多 App(比如 Dropbox、网易云音乐、Clipy)都使用它来常驻状态栏,并提供快捷操作。

可以用 NSStatusBar 创建一个常驻菜单图标,并附加菜单或行为。

状态栏菜单

import AppKit
import SwiftUI

class StatusBarController {
    private var statusItem: NSStatusItem!
    
    init() {
        // 创建系统菜单栏图标
        statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
        if let button = statusItem.button {
            button.image = NSImage(named: "templateIcon")
        }
        
        // 创建菜单
        let menu = NSMenu()
        
        let openItem = NSMenuItem(title: "打开 App", action: #selector(openApp), keyEquivalent: "o")
        openItem.target = self
        menu.addItem(openItem)
        
        menu.addItem(NSMenuItem.separator())
        
        let quitItem = NSMenuItem(title: "退出", action: #selector(NSApp.terminate(_:)), keyEquivalent: "q")
        menu.addItem(quitItem)
        
        let hideItem = NSMenuItem(title: "隐藏状态栏", action: #selector(removeStatusItem), keyEquivalent: "h")
        hideItem.target = self
        menu.addItem(hideItem)
        
        statusItem.menu = menu
        
    }
    
    @objc func openApp() {
        print("打开 App")
        NSApp.activate(ignoringOtherApps: true)
    }
    
    @objc func removeStatusItem() {
        if let item = statusItem {
            NSStatusBar.system.removeStatusItem(item)
            statusItem = nil
        }
    }
}

配合 SwiftUI 使用方法

可以在 AppDelegate 中初始化:

import AppKit
import SwiftUI

class AppDelegate: NSObject, NSApplicationDelegate {
    var statusBarController: StatusBarController?

    func applicationDidFinishLaunching(_ notification: Notification) {
        statusBarController = StatusBarController()
    }
}

在入口文件中使用:

@main
struct MyApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

状态栏详解

StatusBarController状态控制器

创建一个 StatusBarController 类,定义状态栏图标对象statusItem,类型为NSStatusItem:

class StatusBarController {
    private var statusItem: NSStatusItem!

在初始化中,创建状态栏图标和菜单。

init() {
    statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)

使用 .variableLength 自动根据内容大小设定宽度(图标 + 文字)。

if let button = statusItem.button {
    button.image = NSImage(named: "templateIcon")
}

设置图标图片,在后面的“设置状态栏图标”部分,会讲到具体的设置图标流程。

使用NSMenu创建菜单对象:

let menu = NSMenu()

添加菜单项:

let openItem = NSMenuItem(title: "打开 App", action: #selector(openApp), keyEquivalent: "o")

“打开 App”:调用 openApp(),并设为快捷键 ⌘O。

let quitItem = NSMenuItem(title: "退出", action: #selector(NSApp.terminate(_:)), keyEquivalent: "q")

“退出”:直接调用系统的 terminate 方法(快捷键 ⌘Q)。

let hideItem = NSMenuItem(title: "隐藏状态栏", action: #selector(removeStatusItem), keyEquivalent: "h")

“隐藏状态栏”:移除状态栏图标。

其中,”打开App”和“隐藏状态栏” 设置了 target = self,而“退出”没有设置target:

let openItem = NSMenuItem(title: "打开 App", action: #selector(openApp), keyEquivalent: "o")
openItem.target = self
menu.addItem(openItem)

let quitItem = NSMenuItem(title: "退出", action: #selector(NSApp.terminate(_:)), keyEquivalent: "q")
menu.addItem(quitItem)

这是因为,“打开App” 和“隐藏状态栏”的action是我自定义的方法,系统不知道找谁调用,所以必须设置target = self。

而“退出”则是直接调用NSApp.terminate(_:)方法,NSApp本身就是系统的全局对象,已经是target。

行为函数:

@objc func openApp() {
    print("打开 App")
    NSApp.activate(ignoringOtherApps: true)
}

@objc func removeStatusItem() {
    if let item = statusItem {
        NSStatusBar.system.removeStatusItem(item)
        statusItem = nil
    }
}

这两个@objc方法,分别为激活主应用窗口和在系统中移除图标。

不了解@objc方法,可以进一步阅读《Swift @objc属性声明》和《Swift #selector语法》。

AppDelegate代理类

class AppDelegate: NSObject, NSApplicationDelegate {
    var statusBarController: StatusBarController?

    func applicationDidFinishLaunching(_ notification: Notification) {
        statusBarController = StatusBarController()
    }
}

AppDelegate 实现了 NSApplicationDelegate。

在应用启动完成后初始化 StatusBarController,显示菜单栏图标。

SwiftUI 应用入口

@main
    struct MyApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

使用 @NSApplicationDelegateAdaptor 将 AppDelegate 注入 SwiftUI 生命周期。

AppDelegate 会自动接收系统生命周期回调。

常用功能

1、添加状态栏图标:NSStatusBar.system.statusItem,用于创建状态栏项目。

let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)

从系统状态栏获取一个图标槽位(NSStatusItem)。

.variableLength 表示宽度可变(根据图标或文字宽度自动变化)。

2、设置图标/文本:statusItem.button?.image / title,显示图标或文本。

if let button = statusItem.button {
    button.image = NSImage(named: "templateIcon")
    button.action = nil
    button.target = self
}

通过button.image可以实现状态栏图标。

3、设置菜单:statusItem.menu,绑定 NSMenu。

4、响应点击:button.action / target,处理点击行为。

if let button = statusItem.button {
    button.image = NSImage(named: "templateIcon")
    button.action = #selector(statusBarButtonClicked)
    button.target = self
}

通常情况下,设置button时,还需要设置button.action / target,这表示点击图标执行自定义的方法。

但是如果设置了menu:

statusItem.menu = menu

那么,点击图标自动弹出菜单,这里就是系统托管模式,不会触发按钮事件。

因此,就可以忽略button.action / target:

if let button = statusItem.button {
    button.image = NSImage(named: "templateIcon")
}

因为,在设置menu后,即使配置 button.action / target,macOS也会忽略按钮的 action 和 target。

5、移除图标:NSStatusBar.system.removeStatusItem(_:),删除状态栏图标。

NSStatusBar.system.removeStatusItem(item)

移除图标后,如果想要恢复状态栏。可以将创建状态栏的代码封装到方法中,重新调用。

设置状态栏图标

因此,在创建状态栏图标时,可以在原有的mac图标基础上进行修改:将图标的背景改为空白区域,状态栏图标没有边距。

修改后,状态栏的图标是包含透明度的PNG图片,状态栏图标目前推荐的格式为PNG或PDF(矢量)。

macOS 状态栏图标推荐尺寸是 18×18 pt(对应 36×36 px @2x)。如果图片太大/太小/没有内边距,可能不会显示或显示异常。

设置后,显示的状态栏图标:

状态栏图标建议使用模板图(template image),模板图能适配深色/浅色模式。

在Assets文件夹中,找到状态栏图标,在右侧选项中找到“Render As”,设置为“Template Image”。

设置后,图标变成模版图,完成状态栏图标的设置。

总结

NSStatusBar 是系统右上角常驻图标的入口,配合 NSMenu 可构建强大的快捷控制。可与 SwiftUI 配合,通过 AppDelegate 初始化,是很多“轻量工具类应用”的标配入口。

如果在状态栏中没有找到图标,可能是图标过大,或者状态栏图标过多,导致应用图标未显示。

如果想要状态栏弹出SwiftUI视图,可以通过NSPopover实现:

let popover = NSPopover()
popover.contentViewController = NSHostingController(rootView: MySwiftUIView())
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)

参考文章

1、NSStatusBar:https://developer.apple.com/documentation/appkit/nsstatusbar

2、Swift @objc属性声明:https://fangjunyu.com/2025/04/06/swift-objc%e5%b1%9e%e6%80%a7%e5%a3%b0%e6%98%8e/

3、Swift #selector语法:https://fangjunyu.com/2025/06/23/swift-selector%e8%af%ad%e6%b3%95/

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注