Dynamic Binding to Checkboxes


Cocoa Bindings are great, but what do you do when you need to bind checkboxes to an arbitrary array of data?

Some time ago, I had a requirement to present a list of checkboxes that corresponded to an arbitrary array of objects. The array could be changed by the user, though not during the time that the checkboxes were presented.

Previously this had been done by heavy use of switch statements. This did not seem like a good idea to me, and I was using Cocoa bindings in most of my UI code. So I decided to see if I could get bindings to work in this case. I did a lot of searching on dynamic bindings and checkboxes and arrays, but came up mostly empty. I finally stumbled on an article at Cocoa Love that gave me the clue I needed.

It turns out I was looking for valueForUndefinedKey and setValue:forUndefinedKey:. With that knowledge, I was able to craft a solution. This may be a fairly unusual problem that no one else will have, but I thought I would blog it anyone in case some other poor soul found themselves in need of it. I'm also including the full code for a sample app making use of this technique. As the license in the project says, I make no warrant about its usefulness, etc. As far as using the techniques themselves, feel free to use them without attribution.

So, here's how it works. First, I have a class for my objects. In the case of my sample code, it's fairly simple:

@interface Dummy : NSObject {
NSString * name;
NSNumber * include;
}
@property(copy) NSString * name;
@property(retain) NSNumber * include;

I'm using Objective-C 2.0 properties, so that's about all there is to the class, just an @synthesize in the implementation.

In the class that controls the checkboxes, I init the array with the contents of a text file. Obviously, the data could come from anywhere. In interface builder, I created a view where the checkboxes would go, and I instantiated an NSObjectController. I connected the content outlet of the NSObjectController to my checkbox controller. I also conntected the checkbox view and the NSObjectController to outlets in my checkbox controller.

In awakeFromNib, I call a simple layout routine which iterates over the source array and creates a checkbox for each item in the array. In addition to the placement calculations, I also bind the checkbox to the NSObjectController:

NSString * keyPath = [NSString stringWithFormat:@"selection.%i",[dummies indexOfObject:aDummy]];
[switchButton bind:@"value" toObject:theController withKeyPath:keyPath options:nil];

I then implemented valueForUndefinedKey and setValue:forUndefinedKey:

- (id)valueForUndefinedKey:(NSString *)key
{
NSUInteger theIndex = [key intValue];
Dummy * aDummy = (Dummy *)[dummies objectAtIndex:theIndex];
return [aDummy include];
}

- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
NSUInteger theIndex = [key intValue];
Dummy * aDummy = (Dummy *)[dummies objectAtIndex:theIndex];
[aDummy setInclude:value];
[self scanForIncluded];
}

scanForIncluded is a routine I use to display the results so you know I'm not lying. You would probably put in whatever action you need to do when a checkbox is clicked.

Now, I'm sure there are better ways to figure out the keys, but this method seems to work well enough for my purposes.

Here are some examples of the code at work. I started with a list of dummies (literally!):

Charlie McCarthy t
Mortimer Snerd f
Jerry Mahoney t
Knucklehead Smith f

The t and f represent true and false. You might say the initial condition includes then dummies based on being a wise-guy. So here is the starting condition:



As expected, the correct dummies are checked and included in the table on the right. Now we click Mortimer's checkbox:



So far, this could have been accomplished by normal static methods. Someone comes in and says we need a more recent dummy, so we add Buffalo Billy to our list of dummies. (He gets a t, too, because he's also a wise-guy):

Charlie McCarthy t
Mortimer Snerd f
Jerry Mahoney t
Knucklehead Smith f
Buffalo Billy t

The next time we run, we get:



so, our technique is, in fact, dynamic.

A word about the sample code. I am using Garbage Collection in this example because I am trying to learn about it and I didn't have to use retains and releases in the code. I'm getting compiler errors, but it still runs. I do not present this as a good way to do things, but it let me get this out quickly.

On the good side, this sample code also shows how to read and parse a tab-delimited text file.

Here's the sample code: Checkboxes.zip

Posted: Thu - March 12, 2009 at 08:50 AM          


©