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

如何为UIMenu设置固定排序,避免自动调整显示顺序?

问题

我在应用中创建了一个按特定顺序填充的UIMenu,希望菜单始终保持填充时的顺序显示。但UIMenu的默认行为会将填充顺序视为「优先级顺序」,有时会调整列表顺序,把优先级最高的第一项靠近打开菜单的按钮,导致列表偶尔显示为倒序。

我知道UIContextMenu可以通过menu.preferredMenuElementOrder = .fixed来固定顺序,但没找到UIMenu的类似设置方法。请问如何为UIMenu设置固定排序或阻止这种自动排序行为?

附上自定义文本字段类中创建和填充菜单的代码片段:

private var dropdownMenu = UIMenu() {
    didSet {
        dropdownButton.menu = dropdownMenu
    }
}

/// configureDropdownMenu() is used to set the menu contents for your dropdown textfield.
/// - Parameter viewModel: Must set the data of your ViewModel in the View layer. The ViewModel data will be used to populate the dropdown menu.
func configureDropdownMenu(with viewModel: CustomPulldownTextFieldViewModel) {
    // Set the field type and the delegate
    self.customFieldType = viewModel.dropdownType
    self.isEnabled = true

    var dropdownOptions: [String: [UIAction]] = [:]
    var dropdownChildren: [UIMenuElement] = []
    
    switch viewModel.dropdownType {
    case .categorized:
        guard let dropdownHeaders = viewModel.pulldownHeaders else {
            fatalError("\nFatal Error: Must pass in headers for categorized dropdown type\n")
        }
        
        for (index, section) in viewModel.sectionOptions.enumerated() {
            let sectionHeader = dropdownHeaders[index]
            dropdownOptions[sectionHeader] = section.map { title in
                UIAction(title: title, state: .off) { [self] _ in
                    textField.text = title
                    self.delegate?.selectedOptionFromList(pulldownField: self, selection: title)
                }
            }
        }
        
        for header in dropdownHeaders {
            // Check if the section is not empty before adding it
            if let sectionActions = dropdownOptions[header], !sectionActions.isEmpty {
                dropdownChildren.append(UIAction(title: header, attributes: [.disabled], state: .off, handler: { _ in }))
                dropdownChildren.append(contentsOf: sectionActions)
            }
        }
        
        let categorizedMenu = UIMenu(options: .singleSelection, children: dropdownChildren)
        self.dropdownMenu = categorizedMenu
        
    case .uncategorized:
        // Uncategorized list MUST only have one section in sectionOptions
        dropdownChildren = viewModel.sectionOptions[0].map { title in
            UIAction(title: title, state: .off) { [self] _ in
                textField.text = title
                self.delegate?.selectedOptionFromList(pulldownField: self, selection: title)
            }
        }
        
        let uncategorizedMenu = UIMenu(options: .displayInline, children: dropdownChildren)
        self.dropdownMenu = uncategorizedMenu
    case .none:
        fatalError("\nFatal Error: Must set a dropdownType in the view model\n")
    }
}
解决方案

1. iOS 16+ 直接设置固定顺序

从iOS 16开始,UIMenu新增了preferredElementOrder属性,用法和UIContextMenu一致,设置为.fixed即可强制菜单保持你定义的元素顺序,不会自动调整:

// 分类菜单修改
let categorizedMenu = UIMenu(options: .singleSelection, children: dropdownChildren)
categorizedMenu.preferredElementOrder = .fixed
self.dropdownMenu = categorizedMenu

// 无分类菜单同理
let uncategorizedMenu = UIMenu(options: .displayInline, children: dropdownChildren)
uncategorizedMenu.preferredElementOrder = .fixed
self.dropdownMenu = uncategorizedMenu

2. 兼容iOS 15及以下:优化结构+启用内联展示

iOS 15及更早版本没有preferredElementOrder,可通过以下两点解决:

  • 用子UIMenu替代禁用的UIAction做分组标题:你当前用禁用的UIAction作为分组头,系统会将其与普通UIAction视为同一层级,容易触发排序调整。改成将每个分组做成独立的子UIMenu,系统会保留子菜单的顺序。
  • 给父菜单添加.displayInline选项:该选项会强制菜单以内联方式展示,不会根据按钮位置反转顺序。

修改后的分类菜单代码如下:

case .categorized:
    guard let dropdownHeaders = viewModel.pulldownHeaders else {
        fatalError("\nFatal Error: Must pass in headers for categorized dropdown type\n")
    }
    
    var sectionMenus: [UIMenu] = []
    for (index, section) in viewModel.sectionOptions.enumerated() {
        let sectionHeader = dropdownHeaders[index]
        let sectionActions = section.map { title in
            UIAction(title: title, state: .off) { [self] _ in
                textField.text = title
                self.delegate?.selectedOptionFromList(pulldownField: self, selection: title)
            }
        }
        // 将每个分组封装为子UIMenu,标题作为菜单标题
        let sectionMenu = UIMenu(title: sectionHeader, children: sectionActions)
        sectionMenus.append(sectionMenu)
    }
    
    // 父菜单添加.displayInline选项固定顺序
    let categorizedMenu = UIMenu(options: [.singleSelection, .displayInline], children: sectionMenus)
    self.dropdownMenu = categorizedMenu

无分类菜单的.displayInline已正确设置,保持即可。无论iOS版本,菜单都会严格按照你定义的顺序展示,不会出现倒序问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 17:34:53