I want to style a NSPopUpButton with my own colors. I've gotten pretty much everything else to work except for the caps at the top and bottom of the menu and I can't get the NSPopUpButton to show an image. Here are a few screenshots of the problem:
Why is the drawn background bigger on my custom view compared to the system NSPopUpButton?
Here is an image of the caps problem:
I can't figure out where those caps are drawn and how I can change their color to match the menu items?
View controller
import Cocoa
let textColor = NSColor(calibratedWhite: 0.9, alpha: 1)
let surfacePrimaryColor = NSColor(calibratedWhite: 0.1, alpha: 1)
let surfaceSecondaryColor = NSColor(calibratedWhite: 0.3, alpha: 1)
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.translatesAutoresizingMaskIntoConstraints = false
let stackView = NSStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
let cell = PopUpButtonCell()
cell.imagePosition = .imageLeading
let icon = NSImage(systemSymbolName: "folder", accessibilityDescription: nil)
cell.image = icon
print("cell.image: \(cell.image)")
let popUpButton = NSPopUpButton()
popUpButton.cell = cell
for title in (Array(1...100).map { "Folder \($0)" }) {
let menuItem = NSMenuItem()
menuItem.title = title
let menuItemView = MenuItemView()
menuItemView.translatesAutoresizingMaskIntoConstraints = false
menuItemView.onAction {
cell.title = title
menuItem.menu?.cancelTracking()
}
menuItem.view = menuItemView
let titleLabel = NSTextField(string: title)
titleLabel.drawsBackground = false
titleLabel.isBezeled = false
titleLabel.isSelectable = false
titleLabel.isEditable = false
titleLabel.maximumNumberOfLines = 1
titleLabel.textColor = textColor
let deleteButton = Button(systemSymbolName: "xmark")
deleteButton.font = NSFont.systemFont(ofSize: 14)
deleteButton.isBordered = false
deleteButton.contentTintColor = textColor
deleteButton.onAction {
popUpButton.removeItem(withTitle: title)
}
let menuItemStackView = NSStackView()
menuItemView.addSubview(menuItemStackView)
menuItemStackView.orientation = .horizontal
menuItemStackView.edgeInsets = NSEdgeInsets(top: 6, left: 10, bottom: 6, right: 10)
menuItemStackView.translatesAutoresizingMaskIntoConstraints = false
menuItemStackView.leadingAnchor.constraint(equalTo: menuItemView.leadingAnchor).isActive = true
menuItemStackView.trailingAnchor.constraint(equalTo: menuItemView.trailingAnchor).isActive = true
menuItemStackView.topAnchor.constraint(equalTo: menuItemView.topAnchor).isActive = true
menuItemStackView.bottomAnchor.constraint(equalTo: menuItemView.bottomAnchor).isActive = true
menuItemStackView.addView(titleLabel, in: .leading)
menuItemStackView.addView(deleteButton, in: .trailing)
popUpButton.menu?.addItem(menuItem)
}
let popUpButton2 = NSPopUpButton()
popUpButton2.addItems(withTitles: Array(1...100).map { "File \($0)" })
stackView.addArrangedSubview(popUpButton)
stackView.addArrangedSubview(popUpButton2)
}
}
Custom button with onAction closure
import AppKit
typealias Listener = () -> Void
class Button: NSButton {
private var listener: Listener?
init(systemSymbolName: String) {
super.init(frame: .zero)
image = NSImage(systemSymbolName: systemSymbolName, accessibilityDescription: nil)
target = self
action = #selector(actionPerformed(_:))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func actionPerformed(_ sender: AnyObject) {
listener?()
}
func onAction(_ closure: @escaping Listener) {
listener = closure
}
}
Custom popup button cell
import Cocoa
class PopUpButtonCell: NSPopUpButtonCell {
override var controlView: NSView? {
didSet {
controlView?.wantsLayer = true
controlView?.layer?.backgroundColor = surfaceSecondaryColor.cgColor
controlView?.layer?.cornerRadius = 4
}
}
// Prevent system background drawing
override func drawBezel(withFrame frame: NSRect, in controlView: NSView) {
}
override func drawTitle(_ title: NSAttributedString, withFrame frame: NSRect, in controlView: NSView) -> NSRect {
let attributedTitle = NSMutableAttributedString(attributedString: title)
let range = NSMakeRange(0, attributedTitle.length)
attributedTitle.addAttributes([NSAttributedString.Key.foregroundColor : textColor], range: range)
return super.drawTitle(attributedTitle, withFrame: frame, in: controlView)
}
}
Why is image nil after setting it on the NSPopUpButton?
How can I change the color of the menu caps?
See setImage: