When a NSTransformer needs to access "main script" properties…

Hello,

I have created a subclass of NSValueTransformer which turns a tableView cell’s text to red if this cell contains a float value inferior to a limit.

This works perfectly and of course I want to push it a little further.

I want the possibility to turn the values to red if the limit value is a variable, not a constant defined into the NSValueTransformer class, just to make it more flexible.

I want the “turn to red” action to be optional, so transform the color only if a “TurnToRed” property is set.

The problem is, these properties are in the main script, not in the NSValueTransformer.

So, what is the best option?

a) Make a + (void) setLowerLimit: [NSNumber *newLowerLimit] class method for the NSValueTransformer (I suppose every instance of this class could access to the class property)? And another +method to set the optional coloring?

b) Access the “main script” properties using “current application”?

The first method seems more object-oriented and could make this class more reusable, but I’m unsure. In this case, I should instantiate a NSTransformer by code to access to the class methods? And which one?

Help me dissipate the fog, thank you!

Hi,

I recommend to use a custom class with a readonly property (e.g. named textColor)
which returns the proper color depending on the value(s).
Then bind Text Color of the table column to arrayController.arrangedObjects.textColor.

A value transformer is not needed.

The custom class is a replacement for the NSDictionary object(s). It can contain all other values
of the dictionary without the constraint to use only objects

Hi Stefan,

I^m not sure to understand: you mean: subclassing NSDisctionary? Or put the new object into the dictionary with a key like “optionalColor”?

No, a new class (subclass of NSObject).
Instead of initializing NSDictionary, create a new instance of the custom class.
The keys of the dictionary are the properties of the class.

Did you download my “testest” sample project some weeks ago? You’ll see what I mean

PS: you can download a version of testest with text coloring here

Yes, I downloaded it. But this means replacing the dictionary with an array of your custom objects, and a big change in the code.

I understand you make every inserted object an observer of the potentially changing property.

Why not to keep the dictionary, and make one of its keys the observer? Say the key “textColor” and the value a NSColor object? Could I bind the other keys’ color to the value of this “textColor” key?

EDIT: P.S. Stefan, you are coding faster than your shadow.

The observer is actually not necessary. See the updated version.

Custom classes are THE advantage of object oriented programming

Aha! your “lowerThreshold” class variable does the trick. As I subclass my NSValueTranformer, I can also add class variables. Your IB action resets this property. So what’s the difference between your MyClass and my ColorTransformer? Both are object-oriented, aren’t they? :slight_smile:

both threshold variables are class variables! They affect all instances of the class.

The difference between NSValueTransformer and the custom class is like a screwdriver and the Victorinox SwissChamp XLT, the value transformer has one function, the number of functions of an NSObject subclass is unlimited

Victorinox SwissChamp XLT

I like the swiss reference :stuck_out_tongue:

I knew you understand exactly the meaning

Stefan,

What’s your telling me is, if I change a class property “textColor” like this:

[MYClass setTextColor: [NSColor greenColor]];

ALL the MYClass instances dispatched in memory will suddenly write in green? Wow! I can’t remember such a thing existed in the Object Pascal version I used long ago. :expressionless:

Please use class variables very carefully, in this case better use instance variables with this method

[arrayOfMyClassInstances makeObjectsPerformSelector:@selector(setTextColor:) withObject:[NSColor greenColor]];

Consider that the textColor method in the sample project works like a read-only property.
It’s called via the bindings while reloading the table data

Mmh. In other terms, what I’m doing is like violating encapsulation? One should always consider class variables as read-only, even if Objective-C allows it for initialization “only”?

Ok Stefan,

I’m playing for a while now with an array full of objects with properties like numbers, editable names, images, check boxes and so on.

Everything is going smooth, the list can be sorted by column, on every line I have buttons which can send messages, check boxes that make the image switch from red to green, a very nice and reactive interface indeed (useless, of course, I’m just playing).

My only question is: what are the advantage of such a new class compared to a good old Applescript record / NSDictionary? Is it that all methods belong to the object itself?

In that case, the advantage could be: no need of value transformers, no need of notifications, everything is playing in the back of the delegate. am I right? In fact I just make an Applescript record gain some flexibility, that’s the point?

So, why not a “dictionary wrapper”, a NSObject subclass whose unique property would be a NSDictionary?

And a last thing: I put XYClass objects into the array. Now, when I want one of them, I know it is an instance of XY, but for the array it’s just an (id)? I can’t do [[myArray lastObject]doXYmethod] without some coercion?

Regards,

Exactly, it’s not just a key/value collection, it’s much more: the “values” can be primitives, it can contain methods, transformers, etc.

Please unglue from the dictionary imagination

Of course you can, the compiler will consider static typing

Hello,

First trouble:

@interface BFTestRecord : NSObject
@property NSString *name;

  • (id) initWithName: (NSString*) aName;
  • (IBAction)click:(id)sender;

#import “BFTestRecord.h”

@implementation BFTestRecord
@synthesize name;

  • (id) initWithName: (NSString *)aName {
    if (![super init]) return nil;
    name = aName;
    return self;
    }
    -(IBAction)click:(id)sender {
    NSLog(@“In object %@”,name);
    }

The button sends the “click” action to the object (BFTestRecord is a blue cube in IB, I linked the button to the “click” method), but the NSLog says:
2012-06-12 22:18:32.263 TestNewClasses[34734:403] In object (null)
So it is sent to the object, but cannot give its name (yet listed correctly into the array). I tried with “self.name”, idem.

What’s wrong?

the usual init routine syntax is


- (id)initWithName:(NSString *)aName {
    self = [super init];
	if (self) {
        self.name = aName;
    }
	return self;
}

if you’re going to return self, assign it to the result of [super init]

It could also depend a bit on the memory management mode.
if you’re not using ARC or GC the property must be retained or copied


@property (copy) NSString *name;

and the initial value must be assigned using the accessor (self.name)

Consider also that instantiating a class via an object controller (blue cube) calls just the init method, not a custom initWith. method. As you probably need multiple instances of the same class, initialize them from the parent class with

[[BFTestRecord alloc] initWithName:@"whatever"];

Hi Stefan

As you probably need multiple instances of the same class, initialize them from the parent class

In that case, how can I connect the button to the object? Do I have to create a button instance, set its target to self and then put it into the array?

I want each line of a table view to look like

ID | Name | Status | [Connect button] | And when the “connect button” is clicked, the status changed.

You have anyway to add your BFTestRecord objects to the (table) array controller.
Connect the selector of the button cell to the click method in the controller (normally app delegate) class
and use something like this to identify the object in the array

BFTestRecord *record = [[arrayController arrangedObjects] objectAtIndex: [sender selectedRow]];

note that the sender is the table view not the button

then send record the message (method) to change the status