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

macOS 13 SwiftUI中MenuBarExtra生命周期事件处理与动态更新问题

解决macOS 13 SwiftUI MenuBarExtra 菜单打开时动态更新内容的问题

问题核心

MenuBarExtra 的内容视图会被系统缓存,首次打开后不会销毁,因此 onAppear 仅在视图初始化时触发一次;关闭菜单时视图并未被移除,onDisappear 也不会被调用。要实现每次打开菜单都更新按钮文本,必须监听菜单的打开/关闭事件。

方案一:监听NSMenu系统通知

通过监听AppKit提供的NSMenu.willOpenNotification通知,在菜单每次打开时触发内容更新逻辑,是最简洁的实现方式。

完整代码示例

import SwiftUI

struct ContentView: View {
    @State private var buttonTitles = ["Item 1", "Item 2", "Item 3"]
    
    var body: some View {
        MenuBarExtra("Example menu title") {
            VStack {
                Button(buttonTitles[0]) {}
                Button(buttonTitles[1]) {}
                Button(buttonTitles[2]) {}
            }
        }
        // 监听菜单即将打开的通知
        .onReceive(NotificationCenter.default.publisher(for: NSMenu.willOpenNotification)) { _ in
            updateButtonTitles()
        }
        // 可选:监听菜单关闭的通知
        .onReceive(NotificationCenter.default.publisher(for: NSMenu.didCloseNotification)) { _ in
            print("菜单已关闭")
        }
    }
    
    private func updateButtonTitles() {
        // 替换为你的动态更新逻辑,比如从数据源获取或随机生成
        let randomSuffix = Int.random(in: 10...99)
        buttonTitles = ["Item 1-\(randomSuffix)", "Item 2-\(randomSuffix)", "Item 3-\(randomSuffix)"]
    }
}

原理说明

  • NSMenu.willOpenNotification:菜单即将打开时系统发送的通知,每次打开都会触发,刚好适配动态更新的需求。
  • 通过onReceive监听通知,修改@State变量后,SwiftUI会自动刷新视图内容。

方案二:自定义NSMenuDelegate实现精细控制

如果需要更复杂的菜单交互逻辑,可以通过NSViewRepresentable包装NSMenu,设置代理监听菜单事件。

完整代码示例

import SwiftUI
import AppKit

struct MenuBarExtraContentView: NSViewRepresentable {
    @Binding var buttonTitles: [String]
    
    func makeNSView(context: Context) -> NSView {
        let view = NSView()
        let menu = NSMenu()
        // 初始化菜单项
        buttonTitles.forEach { title in
            menu.addItem(NSMenuItem(title: title, action: nil, keyEquivalent: ""))
        }
        // 设置代理
        menu.delegate = context.coordinator
        view.menu = menu
        return view
    }
    
    func updateNSView(_ nsView: NSView, context: Context) {
        // 更新已有菜单项的文本
        guard let menu = nsView.menu else { return }
        for (index, item) in menu.items.enumerated() {
            if index < buttonTitles.count {
                item.title = buttonTitles[index]
            }
        }
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    class Coordinator: NSObject, NSMenuDelegate {
        let parent: MenuBarExtraContentView
        
        init(_ parent: MenuBarExtraContentView) {
            self.parent = parent
        }
        
        // 菜单即将打开时触发
        func menuWillOpen(_ menu: NSMenu) {
            let randomSuffix = Int.random(in: 10...99)
            parent.buttonTitles = ["Item 1-\(randomSuffix)", "Item 2-\(randomSuffix)", "Item 3-\(randomSuffix)"]
        }
    }
}

// 使用示例
struct ContentView: View {
    @State private var buttonTitles = ["Item 1", "Item 2", "Item 3"]
    
    var body: some View {
        MenuBarExtra("Example menu title") {
            MenuBarExtraContentView(buttonTitles: $buttonTitles)
        }
    }
}

注意事项

  • 两种方案均依赖AppKit API,仅适用于macOS平台。
  • 若更新逻辑涉及异步操作(如网络请求),需确保在menuWillOpen回调内处理,保证菜单打开前完成内容更新。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 21:10:30