editmenuitemnspopupbutton

Swift: How to change NSPopupButton edit menu item text


I have a NSPopupButton on my GUI and im looking for a way to programmatically change the text of a specific item:

'item 1'
'item 2' ---> 'item b'
'item 3'


Solution

  • The direct answer to your question is that you can access the items via NSPopUpButton's menu property, and then the items property on the resulting menu. But don't stop reading here, because that's not the way I'd do it.

    Most likely, the reason you want to change the menu item is to reflect the status of something in your model. Perhaps the title is context-sensitive and should change depending on the current selection, or something similar. You can often do this without needing an outlet to the NSPopUpButton at all, simply by doing it in the menu item target's validateMenuItem method:

    override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
        if menuItem.action == #selector(someAction:) {
            if self.someCondition {
                menuItem.title = "Foo"
            } else {
                menuItem.title = "Bar"
            }
        }
    
        return super.validateMenuItem(menuItem)
    }
    

    Not only does this simplify your design and reduce the level to which your program logic needs to know about how your UI is set up, it's also more flexible, since it'll work for any menu item that points to the same target and action. If you decide to move the menu item out of the pop-up button and into the standard menu bar, a toolbar-based menu, or something else, or if you want to have multiple menu items in several of these places, they'll all work exactly the same way without you having to write any new code.

    Another trick that I sometimes use is to populate the menu item's title via Cocoa Bindings. This also has the effect of decoupling the UI from the interface, but is a little more advanced of a topic that you might not want to jump into just yet.

    EDIT: Now that I know your use case, that's exactly the sort of thing I tend to use Cocoa Bindings for :-) Something like:

    class MyViewController: NSViewController {
        ...
    
        // make these @objc and dynamic so that they are KVC-compliant
        @objc dynamic var password: String
        @objc dynamic var bits: Int
    
        // register our password strength as dependent on 'password' and 'bits'
        @objc private static let keyPathsForValuesAffectingPasswordStrength: Set<String> = [
            #keyPath(password),
            #keyPath(bits)
        ]
    
        @objc var passwordStrength: String {
            // obviously you'll do a better test than this in real life ;-)
            if self.password.count >= 8 {
                return NSLocalizedString("Strong", comment: "Strong")
            } else {
                return NSLocalizedString("Weak", comment: "Weak")
            }
        }
    
        ...
    }
    

    Basically, what's going on here is:

    Now, we just go to Interface Builder, and bind our secure password field to the password property like this:

    enter image description here

    Then, we bind our slider to the bits property like this:

    enter image description here

    And for our label, we get a bit fancier and set a pattern involving both passwordStrength and bits:

    enter image description here

    And so, with hardly any code except for a few property definitions, we get this:

    enter image description here

    Pretty slick, eh?