You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在基于NSApplicationDelegate的AppKit应用中使用MenuBarExtra Scene

给传统AppKit应用添加类MenuBarExtra的SwiftUI状态栏UI

传统AppKit应用(以@main标记的NSApplicationDelegate为入口)没法直接使用SwiftUI的MenuBarExtra Scene,因为后者是基于SwiftUI的App生命周期(App协议)设计的。不过可以通过NSStatusItem + NSHostingController的组合实现完全一致的效果,具体步骤如下:

1. 定义SwiftUI状态栏内容视图

先编写你需要的自定义UI,比如带交互元素的菜单视图:

import SwiftUI

struct StatusBarMenuView: View {
    @Binding var isMenuOpen: Bool
    
    var body: some View {
        VStack(spacing: 12) {
            Text("自定义状态栏菜单")
                .font(.headline)
            
            Button(action: {
                print("执行操作")
                isMenuOpen = false // 操作完成后关闭菜单
            }) {
                Text("点击执行")
                    .padding(.horizontal, 20)
                    .padding(.vertical, 8)
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(8)
            }
            
            Button(action: {
                isMenuOpen = false
            }) {
                Text("关闭")
                    .foregroundColor(.red)
            }
        }
        .padding(20)
        .frame(width: 250)
    }
}

2. 在AppDelegate中初始化状态栏项与托管控制器

在你的NSApplicationDelegate子类里,添加状态栏项和SwiftUI视图托管控制器的属性,并在应用启动时完成初始化:

import Cocoa
import SwiftUI

@main
class AppDelegate: NSObject, NSApplicationDelegate {
    var window: NSWindow!
    
    // 状态栏项实例
    private var statusItem: NSStatusItem!
    // 托管SwiftUI视图的控制器
    private var hostingController: NSHostingController<StatusBarMenuView>!
    // 控制菜单展开/收起的状态变量
    private var isMenuOpen = false

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // 创建状态栏项
        statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
        // 设置状态栏图标(可替换为自定义图标)
        if let button = statusItem.button {
            button.image = NSImage(systemSymbolName: "gearshape", accessibilityDescription: "设置")
            button.action = #selector(toggleMenu(_:))
        }
        
        // 初始化SwiftUI视图的托管控制器
        let menuView = StatusBarMenuView(isMenuOpen: $isMenuOpen)
        hostingController = NSHostingController(rootView: menuView)
        
        // 配置弹出窗口样式,模拟MenuBarExtra的非激活面板特性
        hostingController.view.window?.styleMask = [.borderless, .nonactivatingPanel]
        hostingController.view.window?.level = .statusBar
        hostingController.view.window?.backgroundColor = .clear
    }
    
    // 切换菜单显示/隐藏的方法
    @objc private func toggleMenu(_ sender: Any?) {
        isMenuOpen.toggle()
        
        if isMenuOpen {
            guard let button = statusItem.button, let window = hostingController.view.window else { return }
            
            // 计算窗口位置,对齐状态栏按钮
            let buttonFrame = button.convert(button.bounds, to: nil)
            let screenFrame = NSScreen.main?.frame ?? NSZeroRect
            let windowOrigin = NSPoint(
                x: buttonFrame.origin.x + (buttonFrame.width - window.frame.width)/2,
                y: screenFrame.height - buttonFrame.origin.y - buttonFrame.height - window.frame.height
            )
            
            window.setFrameOrigin(windowOrigin)
            window.makeKeyAndOrderFront(nil)
        } else {
            hostingController.view.window?.orderOut(nil)
        }
    }
}

3. 添加外部点击关闭菜单的逻辑

为了让用户点击菜单外部时自动关闭,需要在applicationDidFinishLaunching中添加全局事件监听:

func applicationDidFinishLaunching(_ aNotification: Notification) {
    // ... 保留之前的初始化代码 ...
    
    // 监听全局鼠标点击事件,点击菜单外部时关闭
    NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDown, .rightMouseDown]) { [weak self] event in
        guard let self = self, self.isMenuOpen else { return }
        
        let window = self.hostingController.view.window!
        let eventLocation = window.convert(event.locationInWindow, from: nil)
        
        if !window.frame.contains(eventLocation) {
            self.isMenuOpen = false
            window.orderOut(nil)
        }
    }
}

关键说明

  • 这种实现完全模拟了MenuBarExtra的核心行为:点击状态栏图标切换菜单显示、点击外部自动关闭,同时支持任意SwiftUI自定义UI,比传统NSMenuItem菜单更灵活。
  • 如果只需要简单的下拉列表菜单,可以直接给NSStatusItem设置传统NSMenu;若要丰富UI,则采用上述方法。

内容的提问来源于stack exchange,提问作者NightFuryLxD

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 06:51:08