如何通过UIContextualAction(侧滑操作)展示UIMenu?
我想通过UITableView的侧滑UIContextualAction展示UIMenu,因为没法直接修改UIContextualAction,所以在它的handler里创建UIButton,通过编程触发点击来弹出菜单。测试发现,如果按钮点击事件只显示Toast,功能正常,但换成UIMenu的代码后,菜单就弹不出来。其他地方手动点击按钮用同样的UIMenu逻辑是正常的,只有这里编程触发不行。
相关代码如下:
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { let actionHandler: UIContextualAction.Handler = { action, view, completion in // Add action here let button = UIButton(frame: view.bounds) button.showsMenuAsPrimaryAction = true button.addTarget(self, action: #selector(self.tapOptions), for: .touchUpInside) button.sendActions(for: .touchUpInside) completion(true) } let action = UIContextualAction(style: .normal, title: nil, handler: actionHandler) action.image = UIImage(named: "testImage") action.backgroundColor = .black return UISwipeActionsConfiguration(actions: [action]) }
@objc func tapOptions(_ sender: UIButton) { let muteAction = UIAction(title: "Mute", image: UIImage(named: "Add")) { _ in // Perform action self.showToast(message: "Mute is tapped", attachTo: .top) } let deleteImage = UIImage(named: "Eye")?.withTintColor(.red) let deleteTitle = NSAttributedString(string: "Delete", attributes: [.foregroundColor: UIColor.red]) let deleteAction = UIAction(title: deleteTitle.string, image: deleteImage) { _ in // Perform action self.showToast(message: "Delete is tapped", attachTo: .top) } deleteAction.setValue(deleteTitle, forKey: "attributedTitle") sender.menu = UIMenu(title: "", children: [muteAction, deleteAction]) }
解决方案
问题出在三个核心点:
- 按钮未添加到视图层级就触发点击,UIMenu需要依托已存在于视图树的控件才能弹出;
- 过早调用
completion(true)导致UIContextualAction的容器视图被立即销毁,菜单失去依附载体; - UIMenu设置时机太晚,应该在触发点击前就绑定到按钮,而非等到点击回调中才配置。
修正后的代码
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { let actionHandler: UIContextualAction.Handler = { action, view, completion in // 1. 创建按钮并添加到视图层级 let button = UIButton(frame: view.bounds) button.showsMenuAsPrimaryAction = true view.addSubview(button) // 2. 提前配置UIMenu,确保触发点击时按钮已绑定菜单 let muteAction = UIAction(title: "Mute", image: UIImage(named: "Add")) { _ in self.showToast(message: "Mute is tapped", attachTo: .top) completion(true) // 菜单操作完成后再销毁上下文视图 } let deleteImage = UIImage(named: "Eye")?.withTintColor(.red) let deleteTitle = NSAttributedString(string: "Delete", attributes: [.foregroundColor: UIColor.red]) let deleteAction = UIAction(title: deleteTitle.string, image: deleteImage) { _ in self.showToast(message: "Delete is tapped", attachTo: .top) completion(true) // 菜单操作完成后再销毁上下文视图 } deleteAction.setValue(deleteTitle, forKey: "attributedTitle") button.menu = UIMenu(title: "", children: [muteAction, deleteAction]) // 3. 触发按钮点击 button.sendActions(for: .touchUpInside) // 不要在这里立即调用completion,避免视图提前销毁 } let action = UIContextualAction(style: .normal, title: nil, handler: actionHandler) action.image = UIImage(named: "testImage") action.backgroundColor = .black return UISwipeActionsConfiguration(actions: [action]) }
代码优化(可选)
如果想保持代码结构清晰,可将菜单配置逻辑抽成独立方法:
@objc func setupButtonMenu(_ button: UIButton, completion: @escaping ()->Void) { let muteAction = UIAction(title: "Mute", image: UIImage(named: "Add")) { _ in self.showToast(message: "Mute is tapped", attachTo: .top) completion() } let deleteImage = UIImage(named: "Eye")?.withTintColor(.red) let deleteTitle = NSAttributedString(string: "Delete", attributes: [.foregroundColor: UIColor.red]) let deleteAction = UIAction(title: deleteTitle.string, image: deleteImage) { _ in self.showToast(message: "Delete is tapped", attachTo: .top) completion() } deleteAction.setValue(deleteTitle, forKey: "attributedTitle") button.menu = UIMenu(title: "", children: [muteAction, deleteAction]) }
在handler中调用:
let button = UIButton(frame: view.bounds) button.showsMenuAsPrimaryAction = true view.addSubview(button) self.setupButtonMenu(button, completion: completion) button.sendActions(for: .touchUpInside)
内容的提问来源于stack exchange,提问作者user21901401
相关产品推荐
相关产品推荐

