Programming, automation, algorithms, macOS, and more.

String List Value Transformer

It’s been a while since I last wrote something — the reason for this is that I’ve been busy working on TextMate, but now that I have a free moment, let me tell you about this general tag value to string transformer I use when I bind radio or popup buttons to my user defaults (to get a string stored in the user defaults instead of an integer).

The NSMatrix and NSPopupButton classes have a few binding properties for the selected item, but the only one which I find useful is selectedTag, since it can stay the same even when changing the GUI (adding, removing, or renaming items).

The “problem” with this property is that it gives an integer. Perhaps a non-issue for some, since we can use enumerations or constants in our code to hide the integer value behind a symbolic name, but when storing the selected item in a property list (as is the case with the user defaults) the symbolic name is lost, making it less human readable.

My solution is to use a value transformer for this binding property, which converts the integer to an NSString and vice versa.

Rather than write a value converter for each and every popup or radio button, I have added a general class to my trusty CocoaExtensions.h which allow me to write:

   andObjects:@"foo", @"bar", @"fud", nil];

This creates and registers an NSValueTransformer subclass which converts the integers 0, 1, and 2 to the strings foo, bar, and fud and also does the opposite conversion. So in Interface Builder I can write MyTransformer in the value transformer field for my radio or popup buttons, and the integer based tag values are restrained to Interface Builder.

Full code for the NSValueTransformer subclass follows.

@interface OakStringListTransformer : NSValueTransformer
   NSArray* stringList;
+ (void)createStringListTransformerWithName:(NSString*)aName andObjects:(id)firstObj, ...;

@implementation OakStringListTransformer
+ (Class)transformedValueClass { return [NSNumber class]; }
+ (BOOL)allowsReverseTransformation { return YES; }

+ (void)createStringListTransformerWithName:(NSString*)aName andObjects:(id)firstObj, ...
   ASSERT(firstObj != nil);

   va_list ap;
   va_start(ap, firstObj);
   NSMutableArray* list = [NSMutableArray array];
   do {
      [list addObject:firstObj];
   } while(firstObj = va_arg(ap, id));

   id transformer = [[OakStringListTransformer new] autorelease];
   [transformer setValue:list forKey:@"stringList"];
   [NSValueTransformer setValueTransformer:transformer forName:aName];

- (id)transformedValue:(id)value
   unsigned i = value ? [stringList indexOfObject:value] : NSNotFound;
   return i != NSNotFound ? [NSNumber numberWithUnsignedInt:i] : nil;

- (id)reverseTransformedValue:(id)value
   unsigned i = value ? [value unsignedIntValue] : NSNotFound;
   return i < [stringList count] ? [stringList objectAtIndex:i] : nil;

{{ numberOfCommentsTitle }}

{{ submitComment.success }}

Error Posting Comment

{{ submitComment.error }}