14

So I have an issue with using a constant variable in the following switch statement in Objective-C.

I have Constants.h with the following:

// Constants.h    
extern NSInteger const TXT_NAME;

And Constants.m as:

// Constants.m
#import "Constants.h"

NSInteger const TXT_NAME        = 1;

Then in TabBasic.m I am trying to use this constant in a switch-case statement:

// TabBasic.m

#import "TabBasic.h"
#import "Constants.h"

... code ...

- (IBAction)saveValue:(id)sender {
    if ([sender isKindOfClass: [UITextField class]]) {
        UITextField *txtField = (UITextField *) sender;

        switch (txtField.tag) {
            case TXT_NAME:
                NSLog(@"Set property name to: %@", txtField.text); 
                break;
        }
    }
}

But unfortunately it is giving me the following two errors on the "case TXT_NAME:" line:

  • Expression is not an integer constant expression
  • Case label does not reduce to an integer constant

Does anyone know what I'm doing wrong? The "tag" variable of a UITextField returns an NSInteger, so I don't see the issue...

Thanks for your help!

Jules
  • 7,148
  • 6
  • 26
  • 50

2 Answers2

22

Quick solution, you should place NSInteger const TXT_NAME = 1; in Constants.h, and don't need anything in Constants.m.

Reason: If you set the value of the constant in the .m, it is not visible by other translation units that only include the .h file. The value of the constant must be known at compile time to be able to be used in a case within a switch.

Update:

The above works when compiling in Objective-C++. You need to have your files end in .mm instead of .m for them to be compiled in Objective-C++ instead of Objective-C.

In order to work in Objective-C, you should define your constant either like this:

#define TXT_NAME 1

Or even better, like this:

enum {TXT_NAME = 1};

Thomas
  • 10,358
  • 4
  • 27
  • 35
  • 5
    This solution will lead to linker errors if constants.h will be imported in several implementation files – Vladimir Jul 05 '11 at 15:41
  • @Vladimir, no it will not lead to linker errors, because constants are statically linked by default. – Thomas Jul 05 '11 at 15:43
  • @Thomas So I did this, but it's still giving me the error: Case label does not reduce to an integer constant – Jules Jul 05 '11 at 15:48
  • @Jules, indeed. I always compile in C++, and the above works well. I have updated my answer. – Thomas Jul 05 '11 at 15:56
  • 4
    @Vladimir, this is in fact only true when compiling in C++. Sorry. – Thomas Jul 05 '11 at 15:59
  • @Thomas Yes that works, but it doesn't make much sense to me. You say it is because the value must be known at compile time. But.. if I leave the code as I posted (both in Constants.h and Constants.m) it works if I change the switch-case to a simple "if(txtField.tag == TXT_NAME) { ... }" Why is that then? – Jules Jul 05 '11 at 16:40
  • 1
    @Jules Because the `==` test in your `if` statement does not require the constant to be known at compile time. The `case:` requires a compile time constant value. – Thomas Jul 05 '11 at 17:23
  • 3
    Using #define for constants is a terrible idea. The point of a constant is to treat text like a symbol, not like characters. – Samuel Goodwin Dec 12 '12 at 01:30
  • Anonymous enum was the best solution for me. Also, it is easily extendible, if I want to add more values in the subclass. – kelin Apr 30 '15 at 08:27
10

I would normally follow what Apple seem to do and define a typedef enum in the .h file like this.

typedef NS_ENUM(NSInteger, PSOption) {
  PSOption1,
  PSOption2,
  PSOption3,
  PSOption4,
};  

You can then use it in your case statement and even pass it into functions as well as a type e.g.

- (void)myMethod:(PSOption)option;

A further advantage of doing this over a #define is code completion and compiler checking

Paul.s
  • 38,494
  • 5
  • 70
  • 88