Assuming that I have a typedef declared in my .h file as such:
typedef enum {
JSON,
XML,
Atom,
RSS
} FormatType;
I would like to build a function that converts the numeric value of the typedef to a string. For example, if the message [self toString:JSON]
was sent; it would return 'JSON'.
The function would look something like this:
-(NSString *) toString:(FormatType)formatType {
//need help here
return [];
}
Incidentally, if I try this syntax
[self toString:FormatType.JSON];
to pass the typedef value to the method, I get an error. What am I missing?
This is really a C question, not specific to Objective-C (which is a superset of the C language). Enums in C are represented as integers. So you need to write a function that returns a string given an enum value. There are many ways to do this. An array of strings such that the enum value can be used as an index into the array or a map structure (e.g. an NSDictionary
) that maps an enum value to a string work, but I find that these approaches are not as clear as a function that makes the conversion explicit (and the array approach, although the classic C
way is dangerous if your enum values are not continguous from 0). Something like this would work:
- (NSString*)formatTypeToString:(FormatType)formatType {
NSString *result = nil;
switch(formatType) {
case JSON:
result = @"JSON";
break;
case XML:
result = @"XML";
break;
case Atom:
result = @"Atom";
break;
case RSS:
result = @"RSS";
break;
default:
[NSException raise:NSGenericException format:@"Unexpected FormatType."];
}
return result;
}
Your related question about the correct syntax for an enum value is that you use just the value (e.g. JSON
), not the FormatType.JSON
sytax. FormatType
is a type and the enum values (e.g. JSON
, XML
, etc.) are values that you can assign to that type.
You can't do it easily. In C and Objective-C, enums are really just glorified integer constants. You'll have to generate a table of names yourself (or with some preprocessor abuse). For example:
// In a header file
typedef enum FormatType {
JSON,
XML,
Atom,
RSS
} FormatType;
extern NSString * const FormatType_toString[];
// In a source file
// initialize arrays with explicit indices to make sure
// the string match the enums properly
NSString * const FormatType_toString[] = {
[JSON] = @"JSON",
[XML] = @"XML",
[Atom] = @"Atom",
[RSS] = @"RSS"
};
...
// To convert enum to string:
NSString *str = FormatType_toString[theEnumValue];
The danger of this approach is that if you ever change the enum, you have to remember to change the array of names. You can solve this problem with some preprocessor abuse, but it's tricky and ugly.
Also note that this assumes you have a valid enum constant. If you have an integer value from an untrusted source, you additionally need to do a check that your constant is valid, e.g. by including a "past max" value in your enum, or by checking if it's less than the array length, sizeof(FormatType_toString) / sizeof(FormatType_toString[0])
.
My solution:
edit: I've added even a better solution at the end, using Modern Obj-C
1.
Put names as keys in an array.
Make sure the indexes are the appropriate enums, and in the right order (otherwise exception).
note: names is a property synthesized as *_names*;
code was not checked for compilation, but I used the same technique in my app.
typedef enum {
JSON,
XML,
Atom,
RSS
} FormatType;
+ (NSArray *)names
{
static NSMutableArray * _names = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_names = [NSMutableArray arrayWithCapacity:4];
[_names insertObject:@"JSON" atIndex:JSON];
[_names insertObject:@"XML" atIndex:XML];
[_names insertObject:@"Atom" atIndex:Atom];
[_names insertObject:@"RSS" atIndex:RSS];
});
return _names;
}
+ (NSString *)nameForType:(FormatType)type
{
return [[self names] objectAtIndex:type];
}
//
2.
Using Modern Obj-C you we can use a dictionary to tie descriptions to keys in the enum.
Order DOES NOT matter.
typedef NS_ENUM(NSUInteger, UserType) {
UserTypeParent = 0,
UserTypeStudent = 1,
UserTypeTutor = 2,
UserTypeUnknown = NSUIntegerMax
};
@property (nonatomic) UserType type;
+ (NSDictionary *)typeDisplayNames
{
return @{@(UserTypeParent) : @"Parent",
@(UserTypeStudent) : @"Student",
@(UserTypeTutor) : @"Tutor",
@(UserTypeUnknown) : @"Unknown"};
}
- (NSString *)typeDisplayName
{
return [[self class] typeDisplayNames][@(self.type)];
}
Usage (in a class instance method):
NSLog(@"%@", [self typeDisplayName]);
Combining @AdamRosenfield answer, @Christoph comment and another trick to handle plain C enums I suggest:
// In a header file
typedef enum {
JSON = 0, // explicitly indicate starting index
XML,
Atom,
RSS,
FormatTypeCount, // keep track of the enum size automatically
} FormatType;
extern NSString *const FormatTypeName[FormatTypeCount];
// In a source file
NSString *const FormatTypeName[FormatTypeCount] = {
[JSON] = @"JSON",
[XML] = @"XML",
[Atom] = @"Atom",
[RSS] = @"RSS",
};
// Usage
NSLog(@"%@", FormatTypeName[XML]);
In the worst case - like if you change the enum but forget to change the names array - it will return nil for this key.
define typedef enum in class header:
typedef enum {
IngredientType_text = 0,
IngredientType_audio = 1,
IngredientType_video = 2,
IngredientType_image = 3
} IngredientType;
write a method like this in class:
+ (NSString*)typeStringForType:(IngredientType)_type {
NSString *key = [NSString stringWithFormat:@"IngredientType_%i", _type];
return NSLocalizedString(key, nil);
}
have the strings inside Localizable.strings file:
/* IngredientType_text */
"IngredientType_0" = "Text";
/* IngredientType_audio */
"IngredientType_1" = "Audio";
/* IngredientType_video */
"IngredientType_2" = "Video";
/* IngredientType_image */
"IngredientType_3" = "Image";
I would use the compiler's # string token (along with macros to make it all more compact):
#define ENUM_START \
NSString* ret; \
switch(value) {
#define ENUM_CASE(evalue) \
case evalue: \
ret = @#evalue; \
break;
#define ENUM_END \
} \
return ret;
NSString*
_CvtCBCentralManagerStateToString(CBCentralManagerState value)
{
ENUM_START
ENUM_CASE(CBCentralManagerStateUnknown)
ENUM_CASE(CBCentralManagerStateResetting)
ENUM_CASE(CBCentralManagerStateUnsupported)
ENUM_CASE(CBCentralManagerStateUnauthorized)
ENUM_CASE(CBCentralManagerStatePoweredOff)
ENUM_CASE(CBCentralManagerStatePoweredOn)
ENUM_END
}
I like the #define
way of doing this:
// Place this in your .h file, outside the @interface block
typedef enum {
JPG,
PNG,
GIF,
PVR
} kImageType;
#define kImageTypeArray @"JPEG", @"PNG", @"GIF", @"PowerVR", nil
// Place this in the .m file, inside the @implementation block
// A method to convert an enum to string
-(NSString*) imageTypeEnumToString:(kImageType)enumVal
{
NSArray *imageTypeArray = [[NSArray alloc] initWithObjects:kImageTypeArray];
return [imageTypeArray objectAtIndex:enumVal];
}
source (source no longer available)
I made a sort of mix of all solutions found on this page to create mine, it's a kind of object oriented enum extension or something.
In fact if you need more than just constants (i.e. integers), you probably need a model object (We're all talking about MVC, right?)
Just ask yourself the question before using this, am I right, don't you, in fact, need a real model object, initialized from a webservice, a plist, an SQLite database or CoreData ?
Anyway here comes the code (MPI is for "My Project Initials", everybody use this or their name, it seems) :
MyWonderfulType.h
:
typedef NS_ENUM(NSUInteger, MPIMyWonderfulType) {
MPIMyWonderfulTypeOne = 1,
MPIMyWonderfulTypeTwo = 2,
MPIMyWonderfulTypeGreen = 3,
MPIMyWonderfulTypeYellow = 4,
MPIMyWonderfulTypePumpkin = 5
};
#import <Foundation/Foundation.h>
@interface MyWonderfulType : NSObject
+ (NSString *)displayNameForWonderfulType:(MPIMyWonderfulType)wonderfulType;
+ (NSString *)urlForWonderfulType:(MPIMyWonderfulType)wonderfulType;
@end
And MyWonderfulType.m
:
#import "MyWonderfulType.h"
@implementation MyWonderfulType
+ (NSDictionary *)myWonderfulTypeTitles
{
return @{
@(MPIMyWonderfulTypeOne) : @"One",
@(MPIMyWonderfulTypeTwo) : @"Two",
@(MPIMyWonderfulTypeGreen) : @"Green",
@(MPIMyWonderfulTypeYellow) : @"Yellow",
@(MPIMyWonderfulTypePumpkin) : @"Pumpkin"
};
}
+ (NSDictionary *)myWonderfulTypeURLs
{
return @{
@(MPIMyWonderfulTypeOne) : @"http://www.theone.com",
@(MPIMyWonderfulTypeTwo) : @"http://www.thetwo.com",
@(MPIMyWonderfulTypeGreen) : @"http://www.thegreen.com",
@(MPIMyWonderfulTypeYellow) : @"http://www.theyellow.com",
@(MPIMyWonderfulTypePumpkin) : @"http://www.thepumpkin.com"
};
}
+ (NSString *)displayNameForWonderfulType:(MPIMyWonderfulType)wonderfulType {
return [MPIMyWonderfulType myWonderfulTypeTitles][@(wonderfulType)];
}
+ (NSString *)urlForWonderfulType:(MPIMyWonderfulType)wonderfulType {
return [MPIMyWonderfulType myWonderfulTypeURLs][@(wonderfulType)];
}
@end
Another solution:
typedef enum BollettinoMavRavTypes {
AMZCartServiceOperationCreate,
AMZCartServiceOperationAdd,
AMZCartServiceOperationGet,
AMZCartServiceOperationModify
} AMZCartServiceOperation;
#define AMZCartServiceOperationValue(operation) [[[NSArray alloc] initWithObjects: @"CartCreate", @"CartAdd", @"CartGet", @"CartModify", nil] objectAtIndex: operation];
In your method you can use:
NSString *operationCheck = AMZCartServiceOperationValue(operation);
Improved @yar1vn answer by dropping string dependency:
#define VariableName(arg) (@""#arg)
typedef NS_ENUM(NSUInteger, UserType) {
UserTypeParent = 0,
UserTypeStudent = 1,
UserTypeTutor = 2,
UserTypeUnknown = NSUIntegerMax
};
@property (nonatomic) UserType type;
+ (NSDictionary *)typeDisplayNames
{
return @{@(UserTypeParent) : VariableName(UserTypeParent),
@(UserTypeStudent) : VariableName(UserTypeStudent),
@(UserTypeTutor) : VariableName(UserTypeTutor),
@(UserTypeUnknown) : VariableName(UserTypeUnknown)};
}
- (NSString *)typeDisplayName
{
return [[self class] typeDisplayNames][@(self.type)];
}
Thus when you'll change enum entry name corresponding string will be changed. Useful in case if you are not going to show this string to user.
Given an enum definition like:
typedef NS_ENUM(NSInteger, AssetIdentifier) {
Isabella,
William,
Olivia
};
We can define a macro to convert an enum value to its corresponding string, as shown below.
#define AssetIdentifier(asset) \
^(AssetIdentifier identifier) { \
switch (identifier) { \
case asset: \
default: \
return @#asset; \
} \
}(asset)
The switch
statement used in the block is for type checking, and also to get the auto-complete support in Xcode.
I had a large enumerated type I wanted to convert it into an NSDictionary
lookup. I ended up using sed
from OSX terminal as:
$ sed -E 's/^[[:space:]]{1,}([[:alnum:]]{1,}).*$/ @(\1) : @"\1",/g' ObservationType.h
which can be read as: 'capture the first word on the line and output @(word) : @"word",'
This regex converts the enum in a header file named 'ObservationType.h' which contains:
typedef enum : int {
ObservationTypePulse = 1,
ObservationTypeRespRate = 2,
ObservationTypeTemperature = 3,
.
.
}
into something like:
@(ObservationTypePulse) : @"ObservationTypePulse",
@(ObservationTypeRespRate) : @"ObservationTypeRespRate",
@(ObservationTypeTemperature) : @"ObservationTypeTemperature",
.
.
which can then be wrapped in a method using modern objective-c syntax @{ }
(as explained by @yar1vn above) to create a NSDictionary
lookup :
-(NSDictionary *)observationDictionary
{
static NSDictionary *observationDictionary;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
observationDictionary = [[NSDictionary alloc] initWithDictionary:@{
@(ObservationTypePulse) : @"ObservationTypePulse",
@(ObservationTypeRespRate) : @"ObservationTypeRespRate",
.
.
}];
});
return observationDictionary;
}
The dispatch_once
boiler-plate is just to ensure that the static variable is initialised in a thread-safe manner.
Note: I found the sed regex expression on OSX odd - when I tried to use +
to match 'one or more' it didn't work and had to resort to using {1,}
as a replacement
I use a variation on Barry Walk's answer, that in order of importance:
EG:
- (NSString*)describeFormatType:(FormatType)formatType {
switch(formatType) {
case JSON:
return @"JSON";
case XML:
return @"XML";
case Atom:
return @"Atom";
case RSS:
return @"RSS";
}
[NSException raise:NSInvalidArgumentException format:@"The given format type number, %ld, is not known.", formatType];
return nil; // Keep the compiler happy - does not understand above line never returns!
}
@pixel added the most brilliant answer here: https://stackoverflow.com/a/24255387/1364257 Please, upvote him!
He uses the neat X macro from the 1960's. (I've changed his code a bit for the modern ObjC)
#define X(a, b, c) a b,
enum ZZObjectType {
XXOBJECTTYPE_TABLE
};
typedef NSUInteger TPObjectType;
#undef X
#define XXOBJECTTYPE_TABLE \
X(ZZObjectTypeZero, = 0, @"ZZObjectTypeZero") \
X(ZZObjectTypeOne, , @"ZZObjectTypeOne") \
X(ZZObjectTypeTwo, , @"ZZObjectTypeTwo") \
X(ZZObjectTypeThree, , @"ZZObjectTypeThree")
+ (NSString*)nameForObjectType:(ZZObjectType)objectType {
#define X(a, b, c) @(a):c,
NSDictionary *dict = @{XXOBJECTTYPE_TABLE};
#undef X
return dict[objectType];
}
That's it. Clean and neat. Thanks to @pixel! https://stackoverflow.com/users/21804/pixel
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