I am using the following NSManagedObject which was automatically generated by Xcode:
@interface Portion : NSManagedObject
{
}
@property (nonatomic, retain) NSNumber * volume;
I would like to create a custom getter/setter to convert between ml/oz depending on what the user has set, that way the database always stores the same value and it is automatically converted to the preferred units. My latest attempt looks like this:
#import "Portion.h"
#import "SettingHandler.h"
#define MILLILITERS_PER_OUNCE 29.5735296
@implementation Portion
@dynamic volume;
- (void) setVolume:(NSNumber *) number {
if ([SettingHandler getUnitsTypeShort] == @"oz") {
[self setValue:number forKey:@"volume"];
} else {
[self setValue:[NSNumber numberWithFloat:[number floatValue] * MILLILITERS_PER_OUNCE] forKey:@"volume"];
}
}
- (NSNumber *) volume {
if ([SettingHandler getUnitsTypeShort] == @"oz") {
return [self valueForKey:@"volume"];
} else {
return [NSNumber numberWithDouble: [[self valueForKey:@"volume"] floatValue] * MILLILITERS_PER_OUNCE];
}
}
The setVolume call ends up invoking itself causing an infinite loop. I'm guessing there is a way to do this but I don't know what it is, any ideas?
Sorry to play devil's advocate, but IMO, it seems like you're trying to address how a value is displayed to the user at too low of a level (in the model object itself; see The Model-View-Controller Design Pattern). Why not use a formatter that works more at the view level to help format the raw NSNumber value to a string that will be presented to the user?
You'd then have a reusable class you could use anywhere you use a number value that represents a volume. The formatter would store a "unitsType" value so it would know how to format the incoming number properly.
I did a quick version by using one of my existing formatters, MDFileSizeFormatter, as a starting point:
#import <Foundation/Foundation.h>
enum {
MDVolumeFormatterMetricUnitsType = 1,
MDVolumeFormatterOurStupidAmericanUnitsType = 2,
MDVolumeFormatterDefaultUnitsType = MDVolumeFormatterMetricUnitsType
};
typedef NSUInteger MDVolumeFormatterUnitsType;
@interface MDVolumeFormatter : NSFormatter {
MDVolumeFormatterUnitsType unitsType;
NSNumberFormatter *numberFormatter;
}
- (id)initWithUnitsType:(MDVolumeFormatterUnitsType)aUnitsType;
@property (assign) MDVolumeFormatterUnitsType unitsType;
@end
Then the .m file:
#import "MDVolumeFormatter.h"
#define MILLILITERS_PER_OUNCE 29.5735296
@implementation MDVolumeFormatter
@synthesize unitsType;
- (id)init {
return [self initWithUnitsType:MDVolumeFormatterDefaultUnitsType];
}
- (id)initWithUnitsType:(MDVolumeFormatterUnitsType)aUnitsType {
if (self = [super init]) {
numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setFormat:@"#,###.#"];
[self setUnitsType:aUnitsType];
}
return self;
}
- (void)dealloc {
[numberFormatter release];
[super dealloc];
}
- (NSString *)stringForObjectValue:(id)anObject {
if ([anObject isKindOfClass:[NSNumber class]]) {
NSString *string = nil;
if (unitsType == MDVolumeFormatterMetricUnitsType) {
string = [[numberFormatter stringForObjectValue:
[NSNumber numberWithFloat:
[(NSNumber *)anObject floatValue] * MILLILITERS_PER_OUNCE]]
stringByAppendingString:@" mL"];
} else {
string = [[numberFormatter stringForObjectValue:anObject] stringByAppendingString:@" oz"];
}
return string;
}
return nil;
}
@end
This could potentially be expanded to do tests on the incoming value and automatically determine the appropriate volume unit. For example, if the floatValue was 16.0, you could use if then logic to return a string of "2.0 cups" instead of 16 oz.
Check out "Custom Attribute and To-One Relationship Accessor Methods" in the Core Data Programming Guide. Basically, you should use the primitive getter/setter methods to access/change the value and wrap those calls with KVO notifications.
You will need to add declarations for the primitive accessors:
@interface Portion (PrimitiveAccessors)
- (NSNumber *)primitiveVolume;
- (void)setPrimitiveVolume:(NSNumber *)number;
@end
Then you need to replace each occurrence of:
[self setValue:number forKey:@"volume"];
with:
[self willChangeValueForKey:@"volume"];
[self setPrimitiveVolume:number];
[self didChangeValueForKey:@"volume"];
And make the corresponding changes in the getter.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With