2

I have several popup buttons whose selected tag is saved in the user defaults (by binding the selected tag in the Bindings inspector). Now instead of saving an integer I would like to save a string value (for the simple reason that it makes the user defaults more "readable" and failsafe), but unfortunately didn't find a way to bind a popup button's selected identifier. Is there a solution to this problem?

Nickkk
  • 2,261
  • 1
  • 25
  • 34
  • Bind the Selected Object. Did you bind the content of the popup button? – Willeke Nov 12 '19 at 10:24
  • I didn't bind the content of the popup button, I created all the items in the xib editor. Do you think that's the only way? – Nickkk Nov 18 '19 at 11:38
  • No, there are many ways but `NSPopUpButton` doesn't have a Selected Identifer binding or a `selectItem(withIdentifer:)` method. If you want to bind Selected Object you must set `representedObject` of the menu items in code or by binding Content Objects. – Willeke Nov 18 '19 at 15:12

1 Answers1

3

The bindings for NSPopupButton can be a little confusing. The various Content* bindings are used to provide the button with its list of possible selections. Content itself is used to provide a list of objects represented by the items in the pop up button. Content Values is used to provide the actual values displayed in the pop up button. For example, Content might be bound to an array of model objects, while Content Values is bound to a specific key path on those objects, for example name, because you want to display the value of the name property of each item in the popup button itself.

Similarly, the bindings for selection correspond to this system. Selected Object means that when a given item is selected, the underlying full object from the Content array will be selected/set on the bound property, not just the simple displayed string (or number, etc.) value. On the other hand, Selected Value will indeed bind just the displayed value.

Taken together, and in your case, where you're not using the content bindings, this means that you have two options:

  1. Bind the Selected Value to user defaults.
  2. Create an underlying model class with both an identifier property and a name (or whatever you want to call it) property. Bind the Content binding to an array of those objects, and the Content Values binding to thatArray.name.

Option 1

This option is much simpler. Just set up the selected values binding and you're done. It has the major disadvantage that the actual displayed string is the thing being stored in user defaults. This means that if you change the wording of an item, that previously stored selection won't be restored, even if it corresponds directly to the newly-worded item. More importantly, it's not a good idea to make localized -- or potentially localized -- strings semantically important.

Option 2

This takes a little more work (and code), but it would solve your problem in a robust, "correct" way. For example:

@objcMembers class Option: NSObject {
  dynamic var name: String
  dynamic var identifier: String

  init(name: String, identifier: String) {
    self.name = name
    self.identifier = identifier
  }
}

class ViewController: UIViewController {
  @objc dynamic var optionsForPopup = [Option(name: "Item A", identifier: "id 1"),
                                       Option(name: "Item B", identifier: "id 2"),
                                       Option(name: "Item C", identifier: "id 3")]
}

Bind:

  • Content to ViewController - optionsForPopup.
  • Content Values to ViewController - optionsForPopup.name.
  • Selected Value to Shared User Defaults Controller - Controller Key: values, Model Key Path: WhateverUserDefaultsKeyYouWant.

Example

I've created an example project that implements option 2 here: https://github.com/armadsen/PopupDemo

Andrew Madsen
  • 21,309
  • 5
  • 56
  • 97