Cocoa Value Transformers

Introduction

For a useful set of free sample Value Transformer subclasses, see CLValueTransformers

In essence, Value Transformers do exactly what the name says: provide a simple way to transform one value into another, for example degrees Celsius to degrees Fahrenheit. This could easily be achieved with a method or indeed a function. Having a whole class to perform such a common operation might seem strange. However, the way Value Transformers are used within the Bindings system makes a lot of sense.

To demonstrate this, we'll make a variation on the classic Currency Converter demo app.

I will assume that you have a grasp of the MVC design pattern and Cocoa Bindings in the following discussion. So what kind of values are we transforming?

Model values

Say you have a variable stored in an instance, (ie an ivar), which you want to display in your interface (ie the View), but transformed in some way. To continue our previous example, say we have a temperature stored in terms of degrees Celsius which we need to display in Fahrenheit. We don't want to affect our original value in Celsius, nor do we want to create any new ivars or accessors. This is the perfect place for a Value Transformer.

Typically the object holding this ivar will be part of our application's Model. If you have followed Key Value Coding, you should have KVC accessors for this ivar. Something like:

-(NSNumber *) degreesCelsius;
-(void) setDegreesCelsius:(NSNumber *)value;

...in your model class. So far so good. This allows you to access this ivar through Bindings in Interface Builder so that we can bind the interface to the model value.

Subclassing NSValueTransformer

In order to use a value transformer in our app, we need to subclass NSValueTransformer. So we create a new Objective-C class in our project named CelsiusToFahrenheitTransformer.

In CelsiusToFahrenheitTransformer.h:

@interface CelsiusToFahrenheitTransformer: NSValueTransformer //2.1
{}
@end

Line 2.1 above subclasses NSValueTransformer as CelsiusToFahrenheitTransformer. As you can see, there's not much more to the interface!

In CelsiusToFahrenheitTransformer.m:

#import "CelsiusToFahrenheitTransformer.h"
@implementation CelsiusToFahrenheitTransformer
+ (Class)transformedValueClass {
    return [NSNumber class];
}
+ (BOOL)allowsReverseTransformation {
    return NO;
}
- (id)transformedValue:(id)value {
	if (value != nil)
	{
		float c = [value floatValue];         //3.16
		float f = ((1.8 * c) + 32);           //3.17
		return [NSNumber numberWithFloat:f];  //3.18
	}
	return [NSNumber numberWithFloat:0.0];    //3.20
}
@end

We have now created our basic value transformer ready for use with Cocoa bindings.

The methods we have written override specific methods of the superclass, NSValueTransfomer. It is required for all subclasses to implement these three methods. There are other methods available as well, such as reverseTransformedValue:, which we won't go into here.

You will notice that lines 3.16 and 3.18 serve to unwrap and then wrap our float in an NSNumber. This is because the Bindings system requires all values to be passed as objects. Of course the actual conversion is done on line 3.17.

Line 3.20 ensures that our value transformer always returns a value, even when the input value is nil. This allows us to be lazy about initializing the degreesCelsius ivar in our Model object. In other situations you may want to return nil or a different default value.

Interface Builder

Open the Main Menu.nib file and create a window with some text entry fields, something like this:

01 Window

Next you need to instantiate your model object in the nib.

Now set up bindings for the first text field in the inspector like this:

02 Bind Value

If you have a good grasp of KVC and Bindings, you will understand that we have just bound the value of this text field to the MyModel variable degreesCelsius. At run time, this field will allow us to view and change the current value of this variable in the model object without any other work.

Next set up bindings for the second text field in the inspector like this:

03 Bind Value

The important difference is that here we have inserted our CelsiusToFahrenheitTransformer into the Value Transformer field. This will transform the value from the model for this text field at runtime, on the fly.

Registering Value Transformers

NSValueTransformer is a bit of a queer duck. Because it provides internal services to the Bindings system, which works through the Objective-C equivalent of black magic, it needs to be treated differently to most every other kind of object.

Specifically, a new Value Transformer must be registered as one of the very first objects at the launch of the app, so it's available to the nib file as it is called.

This involves adding some code to your app Controller object.

#import "MyController.h"
#import "CelsiusToFahrenheitTransformer.h"
@implementation MyController
+(void) initialize {
	[super initialize];
	[self initialiseValueTransformers];
}
+(void) initialiseValueTransformers {
	CelsiusToFahrenheitTransformer * celsiusToFahrenheitTransformer =
	[[[CelsiusToFahrenheitTransformer alloc] init] autorelease];
	[CelsiusToFahrenheitTransformer setValueTransformer:
	        celsiusToFahrenheitTransformer
	        forName:@"CelsiusToFahrenheitTransformer"];
}	
@end

This Controller must also be instantiated in the nib.

When the application launches, before everything else is set up, the initialize class method is called on each object in the nib file. Our code above guarantees that the CelsiusToFahrenheitTransformer will be in place before the Bindings system is set up.

You should now be able to build and run the application.

Conclusion

In this article, we have created a working Temperature Converter application with very little code. We may have glossed over a few details, but we have covered the salient points of setting up a Value Transformer for use with Cocoa Bindings.

Download

Download the ValueTransformerExample for Xcode 3.x (24KB).

References

I've been trying to modify

I've been trying to modify this example to a simple multiplication one. Can anyone help?

Added a text box so when two inputted number does the product operation then output the answer out instantly.

Submitted by Anonymous (not verified) on Wed, 07/28/2010 - 09:22.

Post new comment

The content of this field is kept private and will not be shown publicly.
Enter the code shown in the image:

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
2 + 12 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.

Donate!





If you like what you find here and wish to support further development of this site, please donate via PayPal. No account required.

Syndicate

Syndicate content

User login

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
2 + 4 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.
Enter the code shown in the image: