如何监听SwiftUI中Menu控件即将打开或已打开的事件
原生SwiftUI的Menu控件没有提供公开的打开/关闭生命周期回调,以下是经过验证的可行实现方式,可根据需求选择:
方案1:无侵入检测已打开/已关闭事件(兼容所有系统版本)
Menu弹出时,内部的内容视图会被加入渲染层级触发onAppear,菜单销毁关闭时触发onDisappear,直接用这个特性就能拿到打开、关闭的完成回调,不需要引入任何第三方依赖,也不会调用非公开API:
struct ContentView: View { var body: some View { Menu("Options") { VStack { Button("Order Now", action: placeOrder) Button("Adjust Order", action: adjustOrder) Button("Cancel", action: cancelOrder) } .onAppear { // 菜单已完成弹出,可在此处执行对应业务逻辑 menuDidOpen() } .onDisappear { // 菜单已完全关闭 menuDidClose() } } } func placeOrder() {} func adjustOrder() {} func cancelOrder() {} func menuDidOpen() { print("菜单打开") } func menuDidClose() { print("菜单关闭") } }
注意:该方案触发时机是菜单完成渲染之后,无法捕获「即将打开」的前置事件。
方案2:捕获点击触发的「即将打开」时机
如果需要拿到用户点击Menu标签、菜单还未弹出的前置时机,可以给Menu绑定同步点击手势,不会打断Menu默认的弹出行为:
struct ContentView: View { var body: some View { Menu("Options") { Button("Order Now", action: placeOrder) Button("Adjust Order", action: adjustOrder) Button("Cancel", action: cancelOrder) } .simultaneousGesture( TapGesture().onEnded { // 用户点击Menu触发按钮,菜单即将弹出 menuWillOpen() } ) } func placeOrder() {} func adjustOrder() {} func cancelOrder() {} func menuWillOpen() { print("菜单即将打开") } }
注意:该方案仅能捕获用户直接点击Menu标签的唤起场景,通过键盘快捷键、代码触发等其他方式唤起Menu时不会执行回调。
方案3:全场景生命周期Hook(覆盖所有唤起方式)
如果需要覆盖所有Menu唤起路径,拿到系统级的完整生命周期回调(即将打开/已打开/即将关闭/已关闭),可以通过运行时方法交换Hook底层实现:
- iOS端:SwiftUI Menu底层基于
UIContextMenuInteraction实现,可交换对应生命周期的代理方法拿到回调 - macOS端:底层基于
NSMenu实现,可通过对应的通知、代理方法拿到全时机回调
注意:该方案涉及运行时Hook非公开交互逻辑,存在系统版本升级后失效的可能,上架App Store需自行评估审核风险。
内容的提问来源于stack exchange,提问作者Santosh Singh
相关产品推荐
相关产品推荐

