macOS下拉菜单NSPopUpButton
macOS下拉菜单NSPopUpButton

macOS下拉菜单NSPopUpButton

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/

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注