macOS 菜单栏应用是一种轻量级的桌面工具,常驻于屏幕顶部的菜单栏中,用户可以随时点击图标快速访问功能。得益于 macOS 13 Ventura 引入的 MenuBarExtra API,使用 SwiftUI 构建一个菜单栏应用变得前所未有的简单。本文将带你从零开始,逐步完成一个完整的菜单栏应用。
为什么选择菜单栏应用?
与传统的窗口应用不同,菜单栏应用具有独特的优势:
- 常驻可见——始终显示在菜单栏中,用户无需切换窗口即可访问
- 轻量快捷——没有复杂的窗口层级,交互路径极短
- 不占空间——不会在 Dock 中占用位置,适合后台常驻工具
常见的菜单栏应用包括天气工具、剪贴板管理器、番茄钟、系统监控等。本文我们将构建一个简洁的倒数日计数器,用于跟踪重要日期。
项目初始化
打开 Xcode,创建一个新的 macOS App 项目:
- 选择 macOS → App 模板
- Interface 选择 SwiftUI
- Language 选择 Swift
在项目设置中,将 Application Scene Manifest 中的 Application has menu bar 取消勾选——因为我们不需要传统的菜单栏,只需要一个菜单栏图标。
App 入口:MenuBarExtra
在 App 的入口文件中,我们使用 MenuBarExtra 场景替代默认的 WindowGroup:
import SwiftUI
@main
struct CountdownApp: App {
var body: some Scene {
MenuBarExtra("倒数日", systemImage: "calendar.badge.clock") {
ContentView()
}
.menuBarExtraStyle(.window)
}
}
这里有几个关键点:
MenuBarExtra的第一个参数是辅助功能标签,第二个是 SF Symbol 图标名称.menuBarExtraStyle(.window)让点击后弹出的是一个 SwiftUI 视图窗口,而非默认的菜单样式- 你也可以使用
.menu样式来呈现一个传统的下拉菜单
构建内容视图
接下来我们实现 ContentView,展示即将到来的重要日期和剩余天数:
struct ContentView: View {
@State private var events: [CountdownEvent] = [
CountdownEvent(name: "春节", date: Calendar.current.date(from: DateComponents(year: 2026, month: 2, day: 17))!),
CountdownEvent(name: "WWDC", date: Calendar.current.date(from: DateComponents(year: 2026, month: 6, day: 1))!),
]
var body: some View {
VStack(spacing: 0) {
Text("倒数日")
.font(.headline)
.padding()
Divider()
ForEach(events) { event in
CountdownRow(event: event)
if event.id != events.last?.id {
Divider().padding(.horizontal)
}
}
Divider()
Button("退出") {
NSApplication.shared.terminate(nil)
}
.padding(8)
}
.frame(width: 280)
}
}
struct CountdownEvent: Identifiable {
let id = UUID()
let name: String
let date: Date
var daysRemaining: Int {
Calendar.current.dateComponents([.day], from: Date(), to: date).day ?? 0
}
}
struct CountdownRow: View {
let event: CountdownEvent
var body: some View {
HStack {
VStack(alignment: .leading, spacing: 2) {
Text(event.name)
.font(.system(size: 14, weight: .medium))
Text(event.date, style: .date)
.font(.system(size: 11))
.foregroundColor(.secondary)
}
Spacer()
Text("\(event.daysRemaining) 天")
.font(.system(size: 16, weight: .bold, design: .rounded))
.foregroundColor(event.daysRemaining <= 7 ? .orange : .blue)
}
.padding(.horizontal, 16)
.padding(.vertical, 10)
}
}
设置与偏好
一个完整的菜单栏应用通常需要设置窗口。我们使用 SwiftUI 的 Settings 场景来实现:
var body: some Scene {
MenuBarExtra("倒数日", systemImage: "calendar.badge.clock") {
ContentView()
}
.menuBarExtraStyle(.window)
Settings {
SettingsView()
}
}
SettingsView 可以通过菜单栏的 "倒数日 → 设置..." 或快捷键 ⌘, 打开。在其中我们可以让用户自定义要跟踪的事件列表,并使用 @AppStorage 持久化数据。
定时更新
由于天数每天会变化,我们需要在午夜自动刷新界面。监听 NSCalendarDayChanged 通知即可:
struct ContentView: View {
@State private var events: [CountdownEvent] = loadEvents()
@State private var refreshID = UUID() // force refresh
var body: some View {
// ... view content
.id(refreshID)
.onReceive(NotificationCenter.default.publisher(for: .NSCalendarDayChanged)) { _ in
refreshID = UUID()
}
}
}
发布到 App Store
要将应用发布到 Mac App Store,需要完成以下步骤:
- 在 Xcode 中配置 Signing & Capabilities,选择你的开发团队
- 在 App Store Connect 中创建新 App 记录
- 配置 App Sandbox 权限(菜单栏应用通常只需要最低权限)
- Archive 并上传到 App Store Connect
- 填写隐私政策、截图和描述
- 提交审核
注意:菜单栏应用在上架时需要提供清晰的用户引导,因为用户可能不知道如何找到菜单栏中的新图标。建议在首次启动时提供一个引导提示。
总结
MenuBarExtra 是 SwiftUI 在 macOS 平台上的一大进步。过去要用 NSStatusBarButton 和 AppKit 混合开发,现在几行 SwiftUI 代码就能搞定。如果你是 SwiftUI 开发者,不妨动手试一试,把经常用到的小工具做成菜单栏应用——这是一种非常适合个人开发者切入 macOS 生态的方式。
完整代码已托管在 GitHub,欢迎参考和 Star ⭐