NSPopUpButton accepts no transformers.

Hello,

Just another boring discovery.

Imagine you want to bind a pop menu to an array of objects (these objects may be dictionaries, Core Data entities or any other class). You bind the array of objects to the menu Content, via a controller (theController.arrangedObjects). This fills your menu with an object reference for each menu item. Then you want something more readable for the displayed items, so you bind the Content Values to some property / key of your objects (for example a name string): theController.arrangedObjects.name.

I had the idea to set my pop menu’s Content Values to the object itself (theController.arrangedObjects, with no key path) and add a NSValueTransformer, which takes the object as entry value and returns a string, based on the objects properties, for example stringWithFormat . After all, this works perfectly for a table view.

No way.

The NSValueTransformer is simply NOT called, and the items show the object default description (not really readable).

Sad. This means you have the following possibilities:

  1. Build a new array of, say, dictionaries, before the menu is displayed and bind this array to the Content Values to a dictionary’s key
  2. Put a new (non atomic) property into your object and bind this property to the Content Values
  3. Subclass the object and override its description method.

Subclassing just to implement this little functionality is painful, and if you subclass a Core Data entity, you have to be very careful (Apple tells there should be no reason to do so, because entities provide all the desired functionalities, and it sounds like a warning: “don’t do this or you’ll run into problems”).

I just can’t understand why the value transformer get called for table views and not for pop menus. Do you?

Regards,

That’s wrong. Subclassing entities is the normal and best way to add functionality.
The implementation is just a bit tricky

Another way is to use custom classes instead of dictionaries as I’ve mentioned a couple of times.
In a custom class you can define a readonly property which returns the concatenated value

Hi Stefan,

I just mentioned the dictionaries to be fair, I don’t use them anymore if I can avoid them.

Subclassing, as you say, IS the “normal and best way to add functionality” – but when the functionality is laughable compared to the cost (here, it is just to put TWO instance variables in ONE menu item), especially when the classes are Core Data entities, I refrain to do so.

More, the method / property I want to override is the object’s description. Apple does not forbid to override it, but tells it’s not recommended – I read “don’t do this”.

I did it anyway :slight_smile: , and it worked, but my subject was just to mention that there is no possibility to “valueTransform” a popMenu item. I found it sad.

The costs for the value transformer are more expensive

In what terms? Speed? Code readability? Classes consistency? Philosophical approach?

It can be useful to create a particular object with some new functionalities, as a descendant of NSObject. Some Controllers maybe, to implement special “Add” or “Remove” behaviors.

But would you really subclass an entity for such a little improvement? Xcode makes a good job in generating the class, the files, the accessors, and set your controllers accordingly. But if you want to get back, and return to the original model. well, it’s better to start form a Time Machine Copy.

Anyway, I did subclass the entity and override the description method, and got what I wanted. But not really satisfied and ready to clear the code as soon as Apple changes the NSPopUpButton class :confused:

Regardless of what you’ve read on some forums, it’s not impossible to use an NSValueTransformer with a popup button – at least in the sense that it gets called. The problem I’ve had, although I don’t know if this is a problem or expected behavior, is that in the transformedValue: method, the “value” argument is not the individual strings in my array (that’s bound to an array controller) like it is when using a transformer with a table view, but the whole array. I created a simple test project with this in the app delegate:

+(void)initialize {
    RDTransformer *transformer = [[RDTransformer alloc] init];
    [NSValueTransformer setValueTransformer:transformer forName:@"testTransformer"];
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    self.theData = @[@{@"name":@"William", @"age":@"24"},@{@"name":@"Thomas", @"age":@"23"},@{@"name":@"Alexander", @"age":@"64"},@{@"name":@"James", @"age":@"47"}];
}

And this in my value transformer subclass:

+ (Class)transformedValueClass {
    return [NSString class];
}

+(BOOL)allowsReverseTransformation {
    return NO;
}

-(id)transformedValue:(id)value {
    NSLog(@"%@",value);
    return value;
}

In IB, I added an NSPopupButton to the window and an array controller to the objects list. The content array of the controller is bound to App Delegate.theData, and the Content Values of the popup button is bound to Array Controller.arrangedObjects.name with the value transformer, testTransformer.

When I run the program, the log from the transformedValue: method is this:

2012-09-20 09:39:35.056 PopupBindingWithTransformer[586:303] (
)
2012-09-20 09:39:35.106 PopupBindingWithTransformer[586:303] (
William,
Thomas,
Alexander,
James
)

So, the transformer is called, and the transformedValue: method is called twice, returning an empty array the first time and the whole array the second time. So, is this normal for a popup?

Hi Ric,

I had discovered this behavior (value as the whole array) as the console logged something like: <the __NSArrayI is not key-value compliant for the key «name»> (in one of my countless attempts to bring this little rascal transformer to work.)

Anyway, this doesn’t look like an “expected behavior”, don’t you think?

Maybe I should send a bug report?

I really don’t know. I was hoping someone here or over at SO could answer that question, but so far, no luck.

Ric

Sorry to awake an old post, but after hours of reading I found a working solution, so I decided to submit it:

  1. Core Data entity subclassing seems the only way to add my new “functionality” (I need a new property, so a category won’t work). I used the “Create NSManagedObject subclass” of the Editor menu to get my header/implementation files properly filled.

  2. Adding of a new property. As it will never be set, it is readonly:

Now for the implementation part:

Note that the “fullName” property does not have to be added to the entity description-- even as transient.

This method will work-- BUT the “fullName” won’t be updated if “cardID” or “name” are changed by the user. We have to make it KVO compliant. For this, I had to redefine a class method (note the strict naming convention):

At last, I can bind the “fullName” property to my popMenu, and it will update if the used changes “cardID” or “name”.

This situation is not so rare: maybe one of you will need to have two properties in one menu item, as a “firstName” and “lastName”.

Independently of Apple Docs:

Core Data Programming Guide
Core Data Framework Reference

I found parts of this solution in the following books:
[b]
Sams Teach Yourself Core Data for Mac® and iOS in 24 Hours, Second Edition, Copyright © 2012 by Pearson Education, Inc.

Core Data, Apple’s API for Persisting Data on Mac OS X, Marcus S. Zarra, The Pragmatic Bookshelf, Raleigh, North Carolina Dallas, Texas
[/b]
Nothing good is made alone.

Regards,

:wink:

Yes, Stefan. Just a bit. :wink: