NSPopUpButton 是 macOS(AppKit)中用于显示一个下拉菜单(Popup menu)的控件。它可以让用户从一组选项中选择一个,是 macOS 中常见的 UI 控件之一。

功能和特点
1、显示为按钮:点击时展开菜单。
2、可自定义选项:支持文本、图像、分隔符等。
3、支持默认选中项:可设置默认值或编程选择。
4、可动态添加或移除项:用代码实时改变菜单内容。
5、支持事件响应:可设置目标和选择器(target-action)机制响应点击。
基本用法
let popup = NSPopUpButton(frame: NSRect(x: 0, y: 0, width: 200, height: 26))
popup.addItems(withTitles: ["PNG", "JPEG", "TIFF"])
popup.selectItem(withTitle: "JPEG")
popup.target = self
popup.action = #selector(popupChanged(_:))
常用方法和属性
1、addItem(withTitle:):添加一个选项。
popup.addItem(withTitle: "PDF")
2、addItems(withTitles:):批量添加选项。
popup.addItems(withTitles: ["A", "B", "C"])
3、selectItem(at:):根据索引选中。
popup.selectItem(at: 1)
如果索引大于等于选项的长度,那么选项默认为空。
例如,只有3个选项,索引为0-2,但selectItem设置为3,那么显示的选项就是空的。

4、selectItem(withTitle:):根据标题选中。
popup.selectItem(withTitle: "JPEG")
5、indexOfSelectedItem:获取当前选中索引
print("formatPopup.indexOfSelectedItem:\(formatPopup.indexOfSelectedItem)") // 输出 1
6、titleOfSelectedItem:获取当前选中项标题。
print("formatPopup.titleOfSelectedItem:\(formatPopup.titleOfSelectedItem ?? "PNG")") // 输出 JPEG
7、:removeAllItems():移除所有选项。
popup.removeAllItems()
移除后,仍然会显示自定义菜单,但是选项变成空。
8、menu:获取底层 NSMenu 对象。
popup.menu?.items
print("formatPopup.menu?.items:\(formatPopup.menu?.items ?? [NSMenuItem()])") // 输出 formatPopup.menu?.items:[<NSMenuItem: 0x600002b39dc0 PNG>, <NSMenuItem: 0x600002b39d50 JPEG>, <NSMenuItem: 0x600002b39960 TIFF>]
响应用户选择(target + action)
popup.target = self
popup.action = #selector(popupChanged(_:))
@objc func popupChanged(_ sender: NSPopUpButton) {
print("选中了:\(sender.titleOfSelectedItem ?? "")")
}
当选择选项时,会调用popupChanged并输出选中的选项。
配合文件对话框
let popup = NSPopUpButton(frame: NSRect(x: 0, y: 0, width: 200, height: 26))
popup.addItems(withTitles: ["PNG", "JPEG", "TIFF"])
let panel = NSSavePanel()
panel.accessoryView = popup

NSSavePanel的accessoryView为自定义视图,将自定义视图设置为NSPopUpButton后,就可以在保存文件对话框中显示下拉菜单。
使用示例
在SwiftUI中,NSPopUpButton可以配合NSSavePanel保存文件(图片、PDF)。
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Color.clear
.frame(width: 400,height:200)
VStack {
Button(action: {
let panel = NSSavePanel()
let formatPopup = NSPopUpButton(frame: NSRect(x: 0, y: 0, width: 200, height: 26))
formatPopup.addItems(withTitles: ["PNG", "JPEG", "TIFF"])
panel.accessoryView = formatPopup
panel.allowedFileTypes = ["png","jpeg","tiff"]
panel.nameFieldStringValue = "image.png"
if panel.runModal() == .OK, let url = panel.url {
let size = NSSize(width: 200, height: 200)
let image = NSImage(size: size)
image.lockFocus()
// 设置红色背景
NSColor.red.set()
NSRect(origin: .zero, size: size).fill()
image.unlockFocus()
guard let tiffData = image.tiffRepresentation,
let bitmapRep = NSBitmapImageRep(data: tiffData),
let pngData = bitmapRep.representation(using: .png, properties: [:]) else {
print("无法转换为 PNG")
return
}
try? pngData.write(to: url)
}
}, label: {
Text("保存图片")
.foregroundColor(.white)
.padding(10)
.background(.blue)
.cornerRadius(10)
})
.buttonStyle(.plain)
}
}
}
}
当点击按钮时,首先创建NSSavePanel和NSPopUpButton。
初始化NSPopUpButton的位置并配置NSPopUpButton的选项:
let formatPopup = NSPopUpButton(frame: NSRect(x: 0, y: 0, width: 200, height: 26))
formatPopup.addItems(withTitles: ["PNG", "JPEG", "TIFF"])
设置NSSavePanel的accessoryView(自定义/附件视图)、允许保存文件格式和文件名称。
panel.accessoryView = formatPopup
当NSSavePanel返回OK并解包返回的路径后,创建空白的红色方块图,保存到路径中。

监听NSPopUpButton
如果想要通过选择附件视图NSPopUpButton的选项,并动态调整保存文件格式的话,就需要实现NSPopUpButton的action方法:
formatPopup.addItems(withTitles: ["PNG", "JPEG", "TIFF"])
formatPopup.target = self
formatPopup.action = #selector(handleFormatChange)
通过监听NSPopUpButton的选项变化,在回调中手动修改。通过设置target和action方法,当修改NSPopUpButton的选项时,就会调用handleFormatChange方法。
在SwiftUI中,如果想要实现handleFormatChange方法,就需要通过 #selector 传递方法引用。这个方法就需要用 @objc 修饰,因此无法在视图结构(Struct)中直接声明,而是需要放在 Class 作用域中。
因此,可以将整个保存提示框的代码提取到外部类中:
class SavePanelController: NSObject {
let panel = NSSavePanel()
let formatPopup = NSPopUpButton(frame: NSRect(x: 0, y: 0, width: 200, height: 26))
let extensions = ["png", "jpeg", "tiff"]
func showSavePanel() {
formatPopup.addItems(withTitles: ["PNG", "JPEG", "TIFF"])
formatPopup.target = self
formatPopup.action = #selector(handleFormatChange)
panel.accessoryView = formatPopup
panel.allowedFileTypes = ["png", "jpeg", "tiff"]
panel.nameFieldStringValue = "image.png"
if panel.runModal() == .OK, let url = panel.url {
let selectedFormat = formatPopup.titleOfSelectedItem
print("保存到: \(url.path)")
print("选择格式: \(selectedFormat ?? "")")
}
}
@objc func handleFormatChange(_ sender: NSPopUpButton) {
let index = sender.indexOfSelectedItem
let selectedExt = extensions[index]
panel.allowedFileTypes = [selectedExt]
let baseName = (panel.nameFieldStringValue as NSString).deletingPathExtension
panel.nameFieldStringValue = "\(baseName).\(selectedExt)"
}
}
在SwiftUI的Button中调用:
let controller = SavePanelController()
Button("保存文件") {
controller.showSavePanel()
}
这样就可以实现修改NSPopUpButton选项时,同步修改文件名称。

在监听NSPopUpButton时,新增了target和action两个属性:
formatPopup.target = self
formatPopup.action = #selector(handleFormatChange)
当切换NSPopUpButton的选项时,就会调用handleFormatChange方法:
@objc func handleFormatChange(_ sender: NSPopUpButton) {
let index = sender.indexOfSelectedItem
let selectedExt = extensions[index]
let baseName = (panel.nameFieldStringValue as NSString).deletingPathExtension
panel.nameFieldStringValue = "\(baseName).\(selectedExt)"
}
这个方法会接收NSPopUpButton这个对象。
首先通过indexOfSelectedItem方法获取当前的选项索引,根据索引获取extensions数组对应的文件后缀(如”png”)。
最后,通过nameFieldStringValue方法,获取NSSavePanel获取去除后缀的文件名称,再将NSSavePanel设置为文件名称+后缀类型的形式。
总结
通过NSPopUpButton可以实现下拉菜单的功能,如果想要根据选择项实现对应的方法,就需要配置target和action选项。
在 SwiftUI 中,因为action的 @objc 方法无法在视图结构中使用,因此需要将代码封装到外部类中调用。
相关文章
Swift @objc属性声明:https://fangjunyu.com/2025/04/06/swift-objc%e5%b1%9e%e6%80%a7%e5%a3%b0%e6%98%8e/