Thomas Krajacic
Thomas Krajacic

Reputation: 2737

How do I change/modify the displayed title of an NSPopUpButton

I would like an NSPopUpButton to display a different title than the title of the menu item that is selected.

Suppose I have an NSPopUpButton that lets the user pick a list of currencies, how can I have the collapsed/closed button show only the currencies abbreviation instead of the menu title of the selected currency (which is the full name of the currency)?

I imagine I can override draw in a subclass (of NSPopUpButtonCell) and draw the entire button myself, but I would prefer a more lightweight approach for now that reuses the system's appearance.

The menu items have the necessary information about the abbreviations, so that's not part of the question.

Upvotes: 4

Views: 1956

Answers (2)

Willeke
Willeke

Reputation: 15589

Subclass NSPopUpButtonCell, override drawTitle(_:withFrame:in:) and call super with the title you want.

override func drawTitle(_ title: NSAttributedString, withFrame frame: NSRect, in controlView: NSView) -> NSRect {
    var attributedTitle = title
    if let popUpButton = self.controlView as? NSPopUpButton {
        if let object = popUpButton.selectedItem?.representedObject as? Dictionary<String, String> {
            if let shortTitle = object["shortTitle"] {
                attributedTitle = NSAttributedString(string:shortTitle, attributes:title.attributes(at:0, effectiveRange:nil))
            }
        }
    }
    return super.drawTitle(attributedTitle, withFrame:frame, in:controlView)
}

In the same way you can override intrinsicContentSize in a subclass of NSPopUpButton. Replace the menu, call super and put the original menu back.

override var intrinsicContentSize: NSSize {
    if let popUpButtonCell = self.cell {
        if let orgMenu = popUpButtonCell.menu {
            let menu = NSMenu(title: "")
            for item in orgMenu.items {
                if let object = item.representedObject as? Dictionary<String, String> {
                    if let shortTitle = object["shortTitle"] {
                        menu.addItem(withTitle: shortTitle, action: nil, keyEquivalent: "")
                    }
                }
            }
            popUpButtonCell.menu = menu
            let size = super.intrinsicContentSize
            popUpButtonCell.menu = orgMenu
            return size
        }
    }
    return super.intrinsicContentSize
}

Upvotes: 3

Thomas Krajacic
Thomas Krajacic

Reputation: 2737

Ok, so I found out how I can modify the title. I create a cell subclass where I override setting the title based on the selected item. In my case I check the represented object as discussed in the question.

final class MyPopUpButtonCell: NSPopUpButtonCell {
override var title: String! {
    get {
       guard let selectedCurrency = selectedItem?.representedObject as? ISO4217.Currency else {
           return selectedItem?.title ?? ""
       }
           return selectedCurrency.rawValue
       }
       set {}
    }
}

Then in my button subclass I set the cell (I use xibs)

override func awakeFromNib() {
    guard let oldCell = cell as? NSPopUpButtonCell else { return }
    let newCell = MyPopUpButtonCell()
    newCell.menu = oldCell.menu
    newCell.bezelStyle = oldCell.bezelStyle
    newCell.controlSize = oldCell.controlSize
    newCell.autoenablesItems = oldCell.autoenablesItems
    newCell.font = oldCell.font
    cell = newCell
}

A downside though is that I have to copy over all attributes of the cell I configured in Interface Builder. I can of course just set the cell class in Interface Builder, which makes this superfluous.

One thing I haven't figured out yet is how I can have the button have the correct intrinsic content size now. It still tries to be as wide as the longest regular title.

And the second thing I haven't figured out is how to make this work with bindings. If the buttons content is provided via Cocoa Bindings then I can only bind the contentValues, and the cell's title property is never called.

Upvotes: 0

Related Questions