Saturday, October 3, 2009

Coloring table entries to reflect status



We're building on a series of posts looking at bindings. The previous items are:

load data from the main bundle
set up the User Interface and bindings
administrative details---a data class and its header file
binding a boolean to a checkbox

Let's try something a little more sophisticated. The data consists of dictionaries with entries for the key "clear" which are boolean values. Actually, they are instances of NSNumber, so that we can store them in an NSArray and write them to disk. What we will do now is to bind the color of the text in the "name" column to the boolean value for "clear."

We could do this in a fairly simple way by adding a new key-value pair (for the color) to the dictionary. But, this is clumsy, because we would have to repeat this step manually each time the user modified the the checkbox in the interface. A more elegant approach involves an NSValueTransformer. I've actually posted about this before but that version was Python (PyObjC). Let's try it with straight Cocoa.

Open the project's nib file in Interface Builder and click on the second column until it's selected, and verify in the identity pane of the Inspector. Click on its binding, and set the Text Color binding as shown:



Now, we need to implement a custom NSValueTransformer. We don't really need to have it shown in IB, but it doesn't hurt. Drag an object cube out to the nib and set its class to ColorTransformer. The label will change automatically. Write the class files as we did previously.



In XCode, change the header file so that we inherit as appropriate:

@interface ColorTransformer : NSValueTransformer {

And this is the implementation of the class:

#import "ColorTransformer.h"

@implementation ColorTransformer

- (id)init {
self = [super init];
if (self == nil) return nil;
return self;
}

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

- (NSColor *)transformedValue:(id)value {
if (nil == value) { return nil; }
bool flag = [value boolValue];
if (!(flag)) {
return [NSColor blueColor];
}
else {
return [NSColor redColor];
}
}

@end


The result is shown at the top of the post. Clicking the checkbox changes the text color. It's that easy.

The two required methods are shown. One is a class method, marked with '+'. Since they are both declared in the superclass's header file (transformedValue with a return value of id), we don't need to re-declare them.

If you want the value transformer to work in both directions---e.g. as shown here---there is another method required, but this is simpler. And in this implementation Interface Builder is "guessing" the class name from our entry under Value Transformer in the binding---we could implement some methods to do this in a more official way.