Swift3X+Xcode8X Cocoa应用:如何实现宽于控件的组合框选项列表
嘿,我之前做Cocoa应用时也碰到过这个组合框下拉宽度的问题——默认下拉列表确实和控件本身一样宽,不过结合你的SIC选择需求,给你整理了具体的解决步骤,亲测在Swift 3和Xcode 8里完全能用:
核心思路是通过NSComboBoxDelegate的代理方法,在下拉列表弹出前动态调整它的宽度。
首先确保你的组合框已经连接了IBOutlet,并且设置了代理(可以在Storyboard里拖线,或者在viewDidLoad里手动设置yourComboBox.delegate = self)。然后实现下面的代理方法:
方法1:固定加宽
如果你想直接给下拉列表加固定宽度,比如比控件宽200pt,可以这么写:
extension YourViewController: NSComboBoxDelegate { func comboBoxWillPopUp(_ notification: Notification) { guard let comboBox = notification.object as? NSComboBox else { return } // 设置想要的下拉宽度,这里比控件宽200 let desiredWidth = comboBox.frame.width + 200 // 拿到组合框的cell和对应的菜单,调整最小宽度 if let popUpCell = comboBox.cell as? NSComboBoxCell, let menu = popUpCell.menu { menu.minimumWidth = desiredWidth } } }
方法2:根据内容自动适配宽度
如果想让下拉列表刚好适配最长的选项内容(避免浪费空间或截断文本),可以计算所有选项的宽度再设置:
extension YourViewController: NSComboBoxDelegate { func comboBoxWillPopUp(_ notification: Notification) { guard let comboBox = notification.object as? NSComboBox else { return } var maxContentWidth: CGFloat = 0 let itemFont = comboBox.font ?? NSFont.systemFont(ofSize: NSFont.systemFontSize) // 遍历所有选项,计算最长文本的宽度 for item in comboBox.cell.items { guard let itemText = item as? String else { continue } let textWidth = itemText.size(withAttributes: [.font: itemFont]).width if textWidth > maxContentWidth { maxContentWidth = textWidth } } // 加20pt边距,同时保证至少比控件宽50pt let desiredWidth = max(maxContentWidth + 20, comboBox.frame.width + 50) if let popUpCell = comboBox.cell as? NSComboBoxCell, let menu = popUpCell.menu { menu.minimumWidth = desiredWidth } } }
这里推荐用自定义数据模型来存储SIC的代码和描述,比直接存字符串拆分更可靠,避免描述里出现分隔符导致出错。
1. 定义SIC数据模型
先创建一个简单的结构体:
struct SICItem { let code: String let description: String }
比如你可以提前加载好所有SIC选项到一个数组里:
var sicItems: [SICItem] = [ SICItem(code: "0111", description: "谷物种植"), SICItem(code: "0112", description: "蔬菜、水果种植"), // 更多选项... ]
2. 实现组合框数据源
给ViewController添加NSComboBoxDataSource扩展,让组合框正确显示选项:
extension YourViewController: NSComboBoxDataSource { // 返回选项总数 func numberOfItems(in comboBox: NSComboBox) -> Int { return sicItems.count } // 返回对应位置的选项对象 func comboBox(_ comboBox: NSComboBox, objectValueForItemAt index: Int) -> Any? { return sicItems[index] } // 关键:让组合框显示选项的code字段 func comboBox(_ comboBox: NSComboBox, itemObjectValueFor item: Any) -> Any? { guard let sicItem = item as? SICItem else { return nil } return sicItem.code } }
别忘了给组合框设置数据源:yourComboBox.dataSource = self(Storyboard拖线或代码设置都可以)。
3. 选中后更新描述文本框
再回到NSComboBoxDelegate,实现选中变化的代理方法:
func comboBoxSelectionDidChange(_ notification: Notification) { guard let comboBox = notification.object as? NSComboBox, let selectedItem = comboBox.selectedItem as? SICItem else { return } // 更新旁边的文本框显示描述 yourDescriptionTextField.stringValue = selectedItem.description }
如果实在不想用自定义模型,也可以用字符串格式(比如"0111 - 谷物种植"),选中后拆分字符串:
func comboBoxSelectionDidChange(_ notification: Notification) { guard let comboBox = notification.object as? NSComboBox, let selectedText = comboBox.stringValue as? String else { return } let parts = selectedText.components(separatedBy: " - ") if parts.count == 2 { comboBox.stringValue = parts[0] yourDescriptionTextField.stringValue = parts[1] } }
不过这种方法风险较高,还是优先用自定义模型。
- 确保在Xcode里给组合框正确设置了
delegate和dataSource,不然代理和数据源方法不会触发; - Swift 3和Xcode 8对这些API的支持是完全没问题的,不需要额外调整语法;
- 如果下拉列表宽度没变化,可以检查一下
menu.minimumWidth的赋值是否生效,偶尔可能需要给UI操作加个微小的延迟(比如用DispatchQueue.main.asyncAfter),不过一般上面的代码就足够了。
内容的提问来源于stack exchange,提问作者TurboPascal

