Objective-C - Using Enum identifiers as string [duplicate] - ios

This question already has answers here:
Convert Objective-C enum constants to string names
(2 answers)
Closed 8 years ago.
The enumeration below is used in several places in a BMI tool :
typedef NS_ENUM (NSInteger, BMIStatus) {
Malnutrition = 1,
Anorexia = 2,
Thinness = 3,
Normal = 4,
Overweight = 5,
Obesity = 6,
Morbid = 7
};
Is there a trick to use "Malnutrition" as a string ? Considering I have an image named "Malnutrition.png" that I want to load with a classical ImageNamed, and without using an intermediary array storing [1] => #"Malnutrition" for example.
My idea would be to use a kind of [UIImage imageNamed:[NSString stringWithFormat:#"%e", Malnutrition]] where %e leads to the enum identifier instead of the associated value.
Thanks.

Unfortunately this is just not possible using Objective-C. However, it is in Swift if you can use Swift instead.
This is historically handled in Apple's code with NSString constants. For example:
UIKIT_EXTERN NSString *const NSFontAttributeName NS_AVAILABLE_IOS(6_0);
If you need to map between the int value and the NSString value, you will need to write a mapping function.
Also, do make sure to prefix your enums and string constants!

If you want a string for debugging purposes, add a method - (NSString*) stringFromBMIStatus, with a switch statement returning various strings, and a default printing the numeric value for unexpected input.
If you want a string that gives you the name of an NSImage for each enum value, add a method - (NSString*) imageNameFromBMIStatus, probably returning nil for unexpected input.

Related

Not able to find equivalent of respondsToSelector:#selector(charValue) in Swift 3

I have done lot of google to find the equivalent of respondsToSelector, still i could find any best solution. kindly suggest me for Any object in Swift3 or 4.
Objetive-C
[(id)object respondsToSelector:#selector(charValue)]
In Swift we have .method for AnyObject data type but for Any data type
you should first type cast then use
(object as? Type)?.charValue()
if your object is not of a type then it nil and never call the charValue()
Reason: You can not write respondsToSelector for the swift-based function. There are 2 reasons.
1) In Objective-c, we do have charValue property in NSNumber class along with initWithChar. Whereas in swift we do not have any such properties in NSNumber class.
#property (readonly) char charValue;
- (NSNumber *)initWithChar:(char)value NS_DESIGNATED_INITIALIZER;
2) respondsToSelector only accepts Objective-C functions in argument.
Try with writing responds(to: #selector on NSNumber and you will found that it only accepts objective-c function and we don't have any such method in Swift NSNumber class.
let numb: NSNumber?
numb?.responds(to: #selector(#objc method))
Rather, You can use the swift string conversion of NSNumber, as:
let number: NSNumber?
let numberString = number?.stringValue ?? ""
// Added default variable "" in case of string conversion becomes nil

In Swift 2, what changed in NSDictionary?

In Swift 2, what exactly changed in the NSDictionary(objects, forKeys) (Objective-C: dictionaryWithObjects:forKeys:) creation?
Apple's prerelease documentation was particularly useless, given that there only is Obj-C code. Even though there's a slight but obvious change in the provided snippet, I don't speak obj-c and haven't made sense out of the change so far.
Here's some code for you guys:
videoDataOutput.videoSettings = NSDictionary(objects: NSNumber(kCVPixelFormatType_32BGRA), forKeys: (kCVPixelBufferPixelFormatTypeKey))
//Working old Swift code; missing argument for parameter 'count' in call on Swift 2
+ (instancetype)dictionaryWithObjects:(NSArray *)objects
forKeys:(NSArray *)keys
//Old Obj-C code
+ (instancetype nonnull)dictionaryWithObjects:(NSArray<ObjectType> * nonnull)objects
forKeys:(NSArray<id<NSCopying>> * nonnull)keys
//New Obj-C code
I've read elsewhere that nonnull is supposed to facilitate compatibility with Swift, but what about <ObjectType>? And what's id?
#Update
Fixed swift code from NSDictionary(objectsAndKeys: ) to NSDictionary(objects: , forKeys:)
You are not using +dictionaryWithObjects:forKeys:, you are using +dictionaryWithObjectsAndKeys:. These are two different methods. +dictionaryWithObjectsAndKeys: is not supported by swift.
Using +dictionaryWithObjects:forKeys: would look like this.
videoDataOutput.videoSettings = NSDictionary(objects: [NSNumber(integer: kCVPixelFormatType_32BGRA)], forKeys: [kCVPixelBufferPixelFormatTypeKey])
NOTE: I also needed to change NSNumber(kCVPixelFormatType_32BGRA) to NSNumber(integer: kCVPixelFormatType_32BGRA)
Because you only have one entry in the dictionary, you can use +dictionaryWithObject:forKey:.
videoDataOutput.videoSettings = NSDictionary(object: kCVPixelFormatType_32BGRA as NSNumber, forKey: kCVPixelBufferPixelFormatTypeKey as NSString)
NOTE 1: I used kCVPixelFormatType_32BGRA as NSNumber instead of NSNumber(integer: kCVPixelFormatType_32BGRA)
NOTE 2: I had to use kCVPixelBufferPixelFormatTypeKey as NSString instead of just kCVPixelBufferPixelFormatTypeKey.
I think the simplest way to do this is to use swift's Dictionary literal.
videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as NSString: kCVPixelFormatType_32BGRA]
I got this to work in XCode 7 Swift 2 using simply
output.videoSettings = [ kCVPixelBufferPixelFormatTypeKey: Int(kCVPixelFormatType_32BGRA) ]
Check out this project for some current code https://github.com/gibachan/OpenCVSample/
There is no real change for the obj-c programmer. It is mostly for the compiler and as you said for cross-compatibility to swift:
Instead of (NSArray *) you now have (NSArray<ObjectType> * nonnull) which means a) the array may not nonnull (not nil) and b) its contents / values must be of type ObjectType.
Instead of (NSArray *) you have (NSArray<id<NSCopying>> * nonnull) which means a) as above and b) its contents must adapt the NSCopying protocol.
Regarding the expected count: the objectsAndKeys is no longer present (docs), you have to use some other function. or just a literal...
Final note regarding id and ObjectType: id refers to any object, you can sage every view or arra or anything as id - it has itself no type information. ObjectType I think refers to generics, it means that you write ObjectType everywhere you want to use a specific type, you dont know the type yet, but it will be the same at every place where ObjectType is written.

How to box int (enum) to object in swift?

I need to cast an int to an object, in Objective-C I could do the following
[row.cellConfig setObject:#(UITextFieldViewModeAlways) forKey:#"textField.rightViewMode"];
What would be the Swift equivalent?
The Swift equivalent of UITextFieldViewModeAlways is
UITextFieldViewMode.Always, which is an enumeration value:
enum UITextFieldViewMode : Int {
case Never
case WhileEditing
case UnlessEditing
case Always
}
You get its underlying integer value with .rawValue.
Integers are automatically "bridged" to NSNumber when passed
to functions taking Objective-C parameters (and Swift strings
bridged to NSString).
So this should work:
row.cellConfig.setObject(UITextFieldViewMode.Always.rawValue,
forKey: "textField.rightViewMode")
For more information, see Using Swift with Cocoa and Objective-C.
Use
row.cellConfig.setObject(NSNumber(UITextFieldViewModeAlways), forKey:"textField.rightViewMode")
You need to create a NSNumber object for that int.
NSNumber *intObj = [NSNumber numberWithInt:num];

Swift set content compression resistance

Using Swift again, am I missing something?
I have this line:
self.itemDescription?.setContentCompressionResistancePriority(UILayoutPriorityRequired, forAxis: UILayoutConstraintAxis.Vertical);
But Xcode is giving me an error:
Undefined symbols for architecture i386: "_UILayoutPriorityRequired"
Another one of Swift's quirks?
Solution: replace UILayoutPriorityRequired with 1000
self.itemDescription?.setContentCompressionResistancePriority(1000, forAxis: UILayoutConstraintAxis.Vertical);
This is not a bug. It is a misunderstanding of how Objective-C libraries are imported into Swift. It should be understood how Swift imports code (even from the Apple UIKIt Libraries) from Objective-C into Swift.
UILayoutPriority is a float. In Objective-C, a couple of values have been pre-defined for us. The pre-defined values appear to be an enum. We might expect that the same enums would be available to us in Swift.
The documentation does seem to imply an enum:
SWIFT (Documentation)
typealias UILayoutPriority = Float
OBJECTIVE-C (Documentation)
enum {
UILayoutPriorityRequired = 1000,
UILayoutPriorityDefaultHigh = 750,
UILayoutPriorityDefaultLow = 250,
UILayoutPriorityFittingSizeLevel = 50,
};
typedef float UILayoutPriority;
But in Xcode, if you ask to see the defintion of one of these enum values (UILayoutPriorityRequired, for example), you will see that they are actually defined in the header file as constant floats.
OBJECTIVE-C (Header file)
typedef float UILayoutPriority;
static const UILayoutPriority UILayoutPriorityRequired NS_AVAILABLE_IOS(6_0) = 1000; // A required constraint. Do not exceed this.
static const UILayoutPriority UILayoutPriorityDefaultHigh NS_AVAILABLE_IOS(6_0) = 750; // This is the priority level with which a button resists compressing its content.
static const UILayoutPriority UILayoutPriorityDefaultLow NS_AVAILABLE_IOS(6_0) = 250; // This is the priority level at which a button hugs its contents horizontally.
So although we may like to think of the pre-defined layout priorities as enum values (as the documentation suggests) the layout priorities are not really defined as enums; they are defined as constant floats.
A hint that there is a mis-match -- for anyone that knows the C programming language -- is that a C enum may only contain int values. The following is legal and will compile:
enum myEnum {
JGCEnum_one = 1, // this is O.K.
JGCEnum_two,
JGCEnum_three
} JGCEnum;
But we can not define floats as values for C enums. The following will not compile:
enum myEnum {
JGCEnum_one = 1.5, // compile error
JGCEnum_two,
JGCEnum_three
} JGCEnum;
Objective-C enums are the same as C enums (Swift enums are different). It is important to know if we are dealing with actual integers or floats. Because integers can be defined using the NS_ENUM macro, which can then be imported to Swift as a Swift enum.
The iBook says
Swift imports as a Swift enumeration any C-style enumeration marked with the NS_ENUM macro. This means that the prefixes to enumeration value names are truncated when they are imported into Swift, whether they’re defined in system frameworks or in custom code.
Excerpt From: Apple Inc. “Using Swift with Cocoa and Objective-C.” iBooks
That means that if UILayoutPriority had been defined as an integer using the NS_ENUM macro, it would have been imported into Swift as Swift enum. This is the case for UILayoutConstraintAxis.
SWIFT (Documentation)
enum UILayoutConstraintAxis : Int {
case Horizontal
case Vertical
}
OBJECTIVE-C (Documentation)
enum {
UILayoutConstraintAxisHorizontal = 0,
UILayoutConstraintAxisVertical = 1
};
typedef NSInteger UILayoutConstraintAxis;
Looking at the Objective-C header file confirms what the documentation says.
OBJECTIVE-C (Header File)
//
// UIView Constraint-based Layout Support
//
typedef NS_ENUM(NSInteger, UILayoutConstraintAxis) {
UILayoutConstraintAxisHorizontal = 0,
UILayoutConstraintAxisVertical = 1
};
So there are at least two ways to know if a pre-defined value you are used to using in Objective-C is available in Swift:
check the documentation
check the header file in Objective-C (found by right-clicking the value and then selecting "Jump to Definition")
There is one more way to see if a typedef you are used to using is a constant or an enum. In code, test to see if the address of the constant exists. Constants have a memory address, while enums do not. See the code below.
// this line will compile and run just fine.
// UILayoutPriorityDefaultHigh is a constant and has a memory address
// the value will be true if the device is running iOS 6.0 or later
// and false otherwise
BOOL predefinedValueIsAvailable = (NULL != &UILayoutPriorityDefaultHigh);
// this line will not compile
// UILayoutConstraintAxisHorizontal is an enum (NOT a constant)
// and does not have a memory address
predefinedValueIsAvailable = (NULL != &UILayoutConstraintAxisHorizontal);
References
Using Layout Priority in Swift
Xcode Documentation (iOS 8.2)
Apple Inc. “Using Swift with Cocoa and Objective-C.” iBooks
One thing to keep in mind is that, even though UILayoutPriority is an alias to Float, you're using Swift and you can use an extension to create semantic constants for these values:
extension UILayoutPriority {
static var Low: Float { return 250.0 }
static var High: Float { return 750.0 }
static var Required: Float { return 1000.0 }
}
Then you're code can read as:
self.itemDescription?.setContentCompressionResistancePriority(UILayoutPriority.Required, forAxis: .Vertical);
I've done this myself in one of my projects and thought it may be useful.
You can also write this:
self.itemDescription?.setContentCompressionResistancePriority(UILayoutPriority(<float number>), forAxis: .Vertical)
Example:
self.itemDescription?.setContentCompressionResistancePriority(UILayoutPriority(750), forAxis: .Vertical)

Objective C : Array

I am new to objective C and trying to learn it. I am trying to write calculator program which performs simple mathematical calculation(addition, subtraction and so forth).
I want to create an array which stores for numbers(double value) and operands. Now, my pushOperand method takes ID as below:
-(void) pushOperand:(id)operand
{
[self.inputStack addObject:operand];
}
when I try to push double value as below:
- (IBAction)enterPressed:(UIButton *)sender
{
[self.brain pushOperand:[self.displayResult.text doubleValue]];
}
It gives my following error: "Sending 'double' to parameter of incompatible type 'id'"
I would appreciate if you guys can answer my following questions:
'id' is a generic type so I would assume it will work with any type without giving error above. Can you please help me understand the real reason behind the error?
How can I resolve this error?
id is a pointer to any class. Hence, it does not work with primitive types such as double or int which are neither pointers, nor objects. To store a primitive type in an NSArray, one must first wrap the primitive in an NSNumber object. This can be done in using alloc/init or with the new style object creation, as shown in the two snippets below.
old style
NSNumber *number = [[NSNumber alloc] initWithDouble:[self.displayResult.text doubleValue]];
[self.brain pushOperand:number];
new style
NSNumber *number = #( [self.displayResult.text doubleValue] );
[self.brain pushOperand:number];
I suggest using it with an NSNumber: Try not to abuse using id where you don't need to; lots of issues can arise if not.
- (void)pushOperand:(NSNumber *)operand
{
[self.inputStack addObject:operand];
}
- (IBAction)enterPressed:(UIButton *)sender
{
[self.brain pushOperand:#([self.displayResult.text doubleValue])];
}

Resources