2010-11-10 3 views
3

J'utilise le NSManagedObject suivant qui a été généré automatiquement par Xcode:Objective-C sur mesure Getter/Setter

@interface Portion : NSManagedObject 
{ 
} 

@property (nonatomic, retain) NSNumber * volume; 

Je voudrais créer un getter personnalisé/setter pour convertir entre ml/oz en fonction de ce que le l'utilisateur a défini, de cette façon la base de données stocke toujours la même valeur et elle est automatiquement convertie en unités préférées. Ma dernière tentative ressemble à ceci:

#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]; 
    } 
} 

L'appel de setVolume finit par s'appeler lui-même provoquant une boucle infinie. Je suppose qu'il y a un moyen de le faire mais je ne sais pas ce que c'est, des idées?

+2

psst.vous voulez probablement dire quelque chose comme: '[@" oz "isEqualToString: [SettingHandler getUnitsTypeShort]]' au lieu de: '[SettingHandler getUnitsTypeShort] == ​​@" oz "' – justin

Répondre

7

Consultez le "Custom Attribute and To-One Relationship Accessor Methods" dans le Core Data Programming Guide. Fondamentalement, vous devez utiliser les méthodes getter/setter primitives pour accéder/modifier la valeur et encapsuler ces appels avec des notifications KVO.

Vous devrez ajouter des déclarations pour les accesseurs primitifs:

@interface Portion (PrimitiveAccessors) 
- (NSNumber *)primitiveVolume; 
- (void)setPrimitiveVolume:(NSNumber *)number; 
@end 

Ensuite, vous devez remplacer chaque occurrence de:

[self setValue:number forKey:@"volume"]; 

avec:

[self willChangeValueForKey:@"volume"]; 
[self setPrimitiveVolume:number]; 
[self didChangeValueForKey:@"volume"]; 

Et faire la changements correspondants dans le getter.

2

Essayez plutôt d'utiliser [self setPrimitiveValue:number forKey:@"volume"];.

8

Désolé de jouer l'avocat de devil, mais IMO, il semble que vous essayez d'expliquer comment une valeur est affichée à l'utilisateur à un niveau trop bas (dans l'objet modèle lui-même, voir The Model-View-Controller Design Pattern). Pourquoi ne pas utiliser un formateur qui fonctionne plus au niveau de la vue pour aider à formater la valeur NSNumber brute à une chaîne qui sera présentée à l'utilisateur?

Vous auriez alors une classe réutilisable que vous pourriez utiliser partout où vous utilisez une valeur numérique représentant un volume. Le formateur stockera une valeur "unitsType" afin qu'il sache comment formater correctement le numéro entrant.

J'ai fait une version rapide en utilisant l'un de mes formatteurs existants, MDFileSizeFormatter, comme point de départ:

#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 

ensuite le fichier .m:

#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 

Cela pourrait être étendu à faire des tests sur la valeur entrante et déterminer automatiquement l'unité de volume appropriée. Par exemple, si floatValue était de 16,0, vous pourriez utiliser si logique alors pour renvoyer une chaîne de "2,0 tasses" au lieu de 16 oz.

+1

+ environ un million. C'est de * loin * la meilleure solution au problème affiché jusqu'ici. Vous aurez besoin d'un numberFromString: implémenter aussi, bien sûr. – JeremyP

2

Je suggérerais encore une autre approche. Décider de la représentation canonique - soit onces ou millilitres. C'est ce que le volume est réellement stocké. Puis déclarer les getters/setters suivants:

- (void) setOunces:(double)ounces; 
- (double) ounces; 

- (void) setMilliliters:(double)milliliters; 
- (double*) milliliters; 

Si le volume canonique est millilitres, alors:

- (void) setOunces:(double)ounces 
{ 
[self setVolume:[NSNumber numberWithDouble:(ounces* MILLILITERS_PER_OUNCE)]]; 
} 

- (double) ounces 
{ 
return [[self volume] doubleValue]/MILLILITERS_PER_OUNCE; 
} 

- (void) setMilliliters:(double)milliliters 
{ 
[self setVolume:[NSNumber numberWithDouble:milliliters]]; 
} 

- (double) milliliters 
{ 
return [[self volume] doubleValue]; 
}