views:

548

answers:

1

Say I have the following array of dictionaries:

{
    {
        isoName => en,
        fullName => English,
        localName => English
    },
    {
        isoName => de,
        fullName => German,
        localName => Deutsch
    },
    {
        isoName => fr,
        fullName => French,
        localName => français
    }
}

How can I bind the fullName values to the values of an NSPopUpButton control but bind the selection's isoName to NSUserDefaults? I have an NSArrayController set up with an array like the above, and my NSPopUpButton is bound to this controller with the model key path as "fullName", but I want to keep the isoName in the NSUserDefaults. Is this possible? Is there a better way to approach this?

A: 

First off, create a model object class representing a language, and make these dictionary pairs properties of the model objects. Dictionaries don't work well with Bindings, in my experience; the Bindings system wants real model objects.

Next, create a controller object to hold these model objects. I'll call it LanguageKeeper. It has three properties:

  • languages (NSArray of Language objects)
  • selectedLanguage
  • selectedLanguageISOName

Expose the last one as a binding. Also, make one of the last two properties derivative of the other. I'd make the ISOName property derivative: the getter will return self.selectedLanguage.ISOName, and setter will perform the look-up for a given ISO name and set self.selectedLanguage to that language object. Make sure to implement keyPathsForValuesAffectingSelectedLanguage and keyPathsForValuesAffectingSelectedLanguageISOName, and have the other property's name in the set that each method returns.

First, bind the NSArrayController's content-array binding to the languages property of the LanguageKeeper.

Then, bind three properties on the pop-up button:

  • content to the NSArrayController's arrangedObjects property, leaving the model key path empty (or set it to self if this causes problems)
  • content values to the same arrangedObjects property, with a model key path of “fullName
  • selected object to the selectedLanguage property of the LanguageKeeper

Finally, bind selectedLanguageISOName on the LanguageKeeper to the appropriate property of the User Defaults Controller. (You can't bind the pop-up button directly to the UDC because the pop-up button needs one of your model objects.)

You could also try binding the pop-up button's content binding to the isoNames and content-values binding to the fullNames of the dictionaries in the array, but I'm very distrustful of using primitive objects as model objects. I've had too many problems from that in the past. Besides, using model objects pays off in the long run, and they're much fun to work with.

So with all of the above in place, here's what happens:

User changes the value in the pop-up button
  1. The pop-up button changes the value of its selected-object property.
  2. That changes the selectedLanguage property of the LanguageKeeper.
  3. That changes the other property (selectedLanguageISOName) of the LanguageKeeper.
  4. That changes the value in user defaults through the UDC.
Some other object changes the value in user defaults
  1. The UDC posts notifications about the value having changed.
  2. That changes the selectedLanguageISOName property of the LanguageKeeper.
  3. That changes the other property (selectedLanguage) of the LanguageKeeper.
  4. That changes the value of the pop-up button's selected-object property.
  5. The user sees the new selection's fullName in the pop-up menu.

If you don't understand any part of this answer, please ask for clarification.

Peter Hosey
Sorry it took so long to get back to you on this one. I finally implemented as you specified (with a few minor differences) and it works very well. Thanks!
dreamlax