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

SwiftUI中不使用Menu如何为Views实现菜单样式?

解决方案

不需要手动逐个添加NSMenuItem,SwiftUI提供了两种贴合需求的方案,既保留ObservableObject状态管理,又能实现MenuBarExtra的原生样式:


方案1:完全使用SwiftUI的MenuBarExtra(推荐)

放弃原AppKit的NSStatusItem逻辑,直接用SwiftUI原生的MenuBarExtra实现,完美适配状态管理和菜单样式:

@main
struct MyApp: App {
    @StateObject private var statusManager = StatusItemManager()
    
    var body: some Scene {
        // 基础用法:固定图标/标题的MenuBarExtra
        MenuBarExtra("MyApp", systemImage: "circle.fill") {
            MyViewWithButtons()
                .environmentObject(statusManager)
        }
        
        // 可变长度标题的重载(替代NSStatusItem.variableLength)
        // MenuBarExtra(isInserted: .constant(true)) {
        //     MyViewWithButtons()
        //         .environmentObject(statusManager)
        // } label: {
        //     Text(statusManager.dynamicTitle) // 动态更新的标题
        // }
    }
}

class StatusItemManager: ObservableObject {
    @Published var someState = false
    @Published var dynamicTitle = "可变标题"
    // 其他业务逻辑
}

struct MyViewWithButtons: View {
    @EnvironmentObject var manager: StatusItemManager
    
    var body: some View {
        // 直接编写菜单内容,无需嵌套SwiftUI Menu组件
        Button("切换状态") {
            manager.someState.toggle()
        }
        
        Text(manager.someState ? "状态:开启" : "状态:关闭")
        
        Divider()
        
        Button("退出应用") {
            NSApplication.shared.terminate(nil)
        }
    }
}

优势:完全融入SwiftUI生态,状态通过EnvironmentObject无缝传递,视图自动响应状态变化,菜单样式完全匹配原生MenuBarExtra效果,不会出现嵌套菜单问题。


方案2:保留AppKit NSStatusItem的兼容方案

如果必须继续使用NSStatusBar的statusItem,可通过正确配置NSHostingView承载SwiftUI视图,避免显示异常:

class StatusItemManager: ObservableObject {
    let statusItem: NSStatusItem
    private let menu = NSMenu()
    
    init() {
        statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
        statusItem.button?.title = "MyApp"
        
        // 配置SwiftUI视图作为菜单内容
        let hostingView = NSHostingView(rootView: MyViewWithButtons()
            .environmentObject(self))
        hostingView.autoresizingMask = [.width, .height]
        
        // 创建NSMenuItem并挂载hostingView
        let menuItem = NSMenuItem()
        menuItem.view = hostingView
        menuItem.height = hostingView.fittingSize.height // 自动适配高度,不要固定frame
        
        menu.addItem(menuItem)
        statusItem.menu = menu
    }
    
    @Published var someState = false
}

注意:MyViewWithButtons内不要嵌套SwiftUI的Menu组件,直接使用Button、Text、Divider等基础视图,NSHostingView会自动将它们布局为菜单选项。


关键提示

  • 禁止在菜单内容视图中嵌套SwiftUI Menu组件,否则会出现二级下拉菜单,不符合预期样式。
  • 动态调整菜单内容时,直接通过ObservedObject的状态做条件渲染即可,SwiftUI会自动更新菜单布局。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 21:53:15