0

In my projects with Core Data I intensively utiliser NArrayController and bindings and it works perfect. Sometimes I need to transform the NSManagedObject's property presentation (it's presentation in UI) based on user choice.

For instance, I have entity 'Event' which has a property 'Distance'. User can choose how to display it: meters, kilometers, miles. For such simple task I use NSValueTransform.

But how to manage a more complex scenario? For example, I have entity 'Fault' which has a property 'errorValue'. User enters in UI a correction value, let's name it as 'faultCorrection'. After that I need to display 'Fault.errorValue' everywhere as 'fault.errorValue - faultCorrection'.

Currently, for this I have some ugly solutions based on NSValueTransform with static properties. Is there any good solution for such tasks ?

sim
  • 756
  • 6
  • 18

2 Answers2

1

I've done some extra research on this topic, seems there isn't perfect solution for this question, you either need to break MVC pattern or use static vars or use userDefaults (as stevesliva has pointed). I've found one more solution. It is possibly to use a particular instance of nsvaluetransform descendant if binding is done in code.

Transformer's declaration:

@interface FaultCorrection : NSValueTransformer

-(void)setCorrection:(NSInteger)value;

@end

The binding, in code are used the following variables:

  • self.tableView - NSTableView
  • self.ctrl - NSArrayController
  • self.transformer - FaultCorrection

-(void)bindIt {
   NSDictionary * opt = nil;

if ( self.transformer ) { opt = [ NSDictionary dictionaryWithObject:self.transformer forKey:NSValueTransformerBindingOption ]; }

[ self.tableView bind:NSContentBinding toObject:self.ctrl withKeyPath:@"arrangedObjects" options:nil ]; [ self.tableView.tableColumns[0] bind:NSValueBinding toObject:self.ctrl withKeyPath:@"arrangedObjects" options:opt ]; }

This method is called when the user changes the faultCorrection:

-(void)update {
  [ self.transformer setCorrection: [self.fieldCorrection intValue]];
  [ self.tableView reloadData ];
}
sim
  • 756
  • 6
  • 18
0

Having a value transform where you bind the view to the entity's property may actually be the most MVC way to separate things. In fact, it's probably the best way to do it because you get two-way binding if you can implement the reverse transform. So, in short, no there's not really a better solution. Just different ones.

But in your situation I might implement another KVC-compliant property on the entity's backing class. Something like displayValue, and bind to that. I find the code easier to follow if I look up the binding in IB, see that it's to "displayValue" and then just find the appropriate methods.

The issue, though, is that it sounds like faultCorrection is part of the app or view state, not the instance/entity state, so any displayValue method would still have to query a singleton somewhere. You said that currently these are static properties. It is likely better to use the bindings-compliant [NSUserDefaultsController sharedUserDefaultsController]

So, all that said, something like this on the entity's class to bind to:

- (NSString*) displayValue
{
    NSUserDefaultsController* defaults = NSUserDefaultsController.sharedUserDefaultsController;
    NSNumber* faultCorrection = [defaults valueForKeyPath:@"faultCorrection"];
    return [NSString stringWithFormat:@"%d",self.errorValue.floatValue - faultCorrection.floatValue];
}
+ (NSSet*) keyPathsForValuesAffectingDisplayValue 
{
    return [NSSet setWithObject:@"errorValue"];
}

You'd have to figure out how to retrigger the displayValue binding KVO if the user changes the faultCorrection value in the sharedUserDefaults, though. How you do that depends on where the user sets it. And you do have the same issue with any static value that your valueTransformers would currently use-- There's no instance keyPathForValuesAffecting... method that you can use to register your static values as dependent keypaths. Having each entity instance KVO observe the sharedUserDefaults is probably overkill if you can just blow the ViewController away and reinit it.

Also, as an aside, this is a very similar topic to that covered by this recent question

Community
  • 1
  • 1
stevesliva
  • 5,351
  • 1
  • 16
  • 39
  • thanks for your answer, it seems that there isn't perfect solution for this task, I've found one more solution and I've posted it as an additional answer, maybe it helps smb. – sim Mar 25 '15 at 14:59