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

如何实现支持多选功能的iOS下拉按钮(Pull-down Button)?

如何用UIButton+UIMenu实现多选下拉菜单?

我已经成功实现支持互斥选项选择的弹出按钮(Pop-up Button),这在HIG的《Pop-up buttons》章节中有相关说明。现在我需要实现类似但支持多选的功能,HIG的弹出按钮页面指出:「若需支持多选等场景,请使用下拉按钮(Pull-down Button)」,但HIG的下拉按钮页面未提及如何实现多选。

我尝试的步骤如下:
先基于弹出按钮代码(可复制到iOS Swift Playground运行):

import UIKit
import PlaygroundSupport

class MyVC: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let items = [ "Option 1", "Option 2", "Option 3", "Option 4" ]
        let actions: [UIAction] = items.map {
            let action = UIAction(title: $0) { action in
                print("Selected \(action.title)")
            }

            return action
        }
        let menu = UIMenu(children: actions)

        var buttonConfig = UIButton.Configuration.gray()
        let button = UIButton(configuration: buttonConfig)
        button.menu = menu
        button.showsMenuAsPrimaryAction = true
        button.changesSelectionAsPrimaryAction = true

        button.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(button)
        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor),
        ])
    }
}

PlaygroundPage.current.liveView = MyVC()

随后修改代码转为下拉按钮:

  • 禁用按钮的changesSelectionAsPrimaryAction属性:
button.changesSelectionAsPrimaryAction = false
  • 为按钮设置标题:
buttonConfig.title = "Select Items"

此时按钮点击可弹出菜单,但无选中标记,我尝试在UIAction的处理块中切换action的state:

let action = UIAction(title: $0) { action in
    print("Selected \(action.title)")
    action.state = action.state == .on ? .off : .on
}

但点击菜单项时程序崩溃,真实iOS应用中的错误信息为:

2023-05-21 10:40:56.038217-0600 ButtonMenu[63482:10716279] *** Assertion failure in -[_UIImmutableAction setState:], UIAction.m:387

2023-05-21 10:40:56.063676-0600 ButtonMenu[63482:10716279] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Action is immutable because it is a child of a menu'

请问是否可以通过UIButton和UIMenu实现多选菜单?若可以,我遗漏了哪些步骤?若不行,应使用什么组件实现多选功能?理想效果为点击按钮弹出菜单,多选后再次点击按钮关闭菜单。


解决方案

可以通过UIButton + UIMenu实现多选菜单,你之前的崩溃是因为UIAction加入菜单后会变为不可变对象,无法直接修改其state属性,且遗漏了开启菜单多选支持的关键配置。以下是正确的实现步骤:

关键修改点

  1. 开启菜单的多选支持:创建UIMenu时,设置options为[.allowsMultipleSelection],系统会自动处理菜单项的选中状态切换,无需手动修改UIAction的state。
  2. 为UIAction设置唯一Identifier:方便后续追踪选中的项(可选,但推荐)。
  3. 维护选中状态(可选):如果需要在外部获取或展示选中的项,可以维护一个集合来记录选中的标识符或标题。

完整可运行代码

import UIKit
import PlaygroundSupport

class MyVC: UIViewController {
    // 维护选中项的标识符集合(可选,用于追踪选中状态)
    private var selectedIdentifiers = Set<UUID>()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let items = [ "Option 1", "Option 2", "Option 3", "Option 4" ]
        let actions: [UIAction] = items.map { item in
            let actionID = UUID()
            let action = UIAction(title: item, identifier: actionID) { [weak self] action in
                guard let self = self else { return }
                // 更新本地选中集合
                if self.selectedIdentifiers.contains(actionID) {
                    self.selectedIdentifiers.remove(actionID)
                } else {
                    self.selectedIdentifiers.insert(actionID)
                }
                print("当前选中项:\(self.selectedIdentifiers.compactMap { id in 
                    items.first { item in action.id == id } 
                })")
            }
            // 初始状态设置(可选,比如默认选中某些项)
            action.state = self.selectedIdentifiers.contains(actionID) ? .on : .off
            return action
        }
        
        // 创建支持多选的菜单
        let menu = UIMenu(
            title: "",
            options: [.allowsMultipleSelection], // 关键:开启多选
            children: actions
        )
        
        var buttonConfig = UIButton.Configuration.gray()
        buttonConfig.title = "Select Items"
        let button = UIButton(configuration: buttonConfig)
        button.menu = menu
        button.showsMenuAsPrimaryAction = true
        button.changesSelectionAsPrimaryAction = false // 下拉按钮需禁用此属性
        
        button.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(button)
        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor),
        ])
    }
}

PlaygroundPage.current.liveView = MyVC()

额外说明

  • 系统会自动处理菜单项的选中/取消选中视觉反馈,无需手动修改UIAction的state。
  • 如果需要动态更新菜单内容(比如根据选中状态修改按钮标题),可以在选中集合变化后重新生成UIMenu并赋值给button.menu。
  • 若你的iOS版本低于14(不推荐),则无法使用UIMenu的多选特性,此时可以考虑使用UITableView或UICollectionView自定义弹出菜单,或者使用第三方组件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 06:18:10