iOS 14下UIBarButtonItem依赖上下文的菜单项配置方案问询
我之前也碰到过一模一样的问题——iOS14给UIBarButtonItem加菜单确实省心,但遇到菜单项要跟着实时上下文变的情况,直接写死menu就完全行不通了。你提到的几个方案里,其实UIDeferredMenuElement是能解决这个问题的,可能是之前用法没找对,我来给你捋捋正确的实现方式:
核心方案:正确使用UIDeferredMenuElement延迟生成菜单项
UIDeferredMenuElement的设计初衷就是应对「菜单内容依赖实时状态」的场景,它会在菜单即将弹出时才执行内容生成逻辑,完美适配你的需求。具体步骤如下:
- 初始化UIBarButtonItem时,给它设置一个包含UIDeferredMenuElement的基础菜单
- 在UIDeferredMenuElement的completion闭包里,根据当前上下文(比如选中内容、页面状态等)生成实际的菜单项,再通过completion回调返回
举个实际代码例子:
// 初始化UIBarButtonItem let dynamicBarButton = UIBarButtonItem(title: "操作", style: .plain, target: nil, action: nil) // 创建延迟加载的菜单元素 let deferredElement = UIDeferredMenuElement { [weak self] completion in guard let self = self else { completion([]) return } // 这里获取当前上下文状态,比如从数据源或页面控件取实时值 let currentSelectedItem = self.getCurrentSelectedItem() // 根据上下文生成对应的菜单项 let dynamicMenuItems: [UIMenuElement] = [ UIAction(title: "编辑「\(currentSelectedItem.name)」", handler: { _ in self.handleEditAction(for: currentSelectedItem) }), UIAction(title: "分享「\(currentSelectedItem.name)」", handler: { _ in self.handleShareAction(for: currentSelectedItem) }), // 可以根据上下文动态添加/隐藏菜单项 currentSelectedItem.isEditable ? UIAction(title: "删除", attributes: .destructive, handler: { _ in self.handleDeleteAction(for: currentSelectedItem) }) : UIMenuElement() ].filter { !($0 is UIMenuElement) } // 过滤空元素 // 将生成的菜单项传给completion,菜单会自动更新显示 completion(dynamicMenuItems) } // 给BarButtonItem设置包含延迟元素的菜单 dynamicBarButton.menu = UIMenu(title: "", children: [deferredElement])
这样每次用户点击/长按弹出菜单时,都会触发闭包里的逻辑,根据当时的实时上下文生成最新的菜单项,完全不用在上下文变化时手动更新menu属性。
为什么你之前觉得UIDeferredMenuElement不适用?
大概率是你尝试直接用它替换整个menu,或者没有把它作为UIMenu的子元素来使用。记住,UIDeferredMenuElement是作为UIMenu的children存在的,它会在菜单即将显示时自动替换成你生成的实际菜单项,用户完全感知不到这个延迟过程。
备选方案:自定义UIButton作为customView
如果确实不想用UIDeferredMenuElement,也可以把UIButton作为UIBarButtonItem的customView,然后监听菜单即将显示的通知来动态更新:
let customButton = UIButton(type: .system) customButton.setTitle("操作", for: .normal) let barButton = UIBarButtonItem(customView: customButton) // 监听菜单将要显示的通知 NotificationCenter.default.addObserver(forName: UIMenu.menuWillShowNotification, object: nil, queue: .main) { [weak self] _ in guard let self = self else { return } // 根据上下文生成新菜单 let updatedMenu = UIMenu(title: "", children: self.generateDynamicMenuItems()) customButton.menu = updatedMenu } // 记得在deinit时移除观察者 deinit { NotificationCenter.default.removeObserver(self) }
不过这个方案需要处理通知的生命周期,不如UIDeferredMenuElement简洁,优先推荐前者。
内容的提问来源于stack exchange,提问作者SOuser
相关产品推荐
相关产品推荐

