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

如何通过UIContextualAction(侧滑操作)展示UIMenu?

问题:编程触发UIContextualAction内的UIButton点击无法显示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])
    }

解决方案

问题出在三个核心点:

  1. 按钮未添加到视图层级就触发点击,UIMenu需要依托已存在于视图树的控件才能弹出;
  2. 过早调用completion(true)导致UIContextualAction的容器视图被立即销毁,菜单失去依附载体;
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 10:12:40