Declaring UI Objects in Switch Cases

2019-03-02 02:31发布

问题:

I've read about the scope of switch cases, being jump labels and all, but the suggested solutions here at SO seem to imply that adding curly braces would circumvent the issue. However, this still doesn't seem to work:

switch (objectType) {

  case label:   //label is an integer constant
    NSLog(@"statement before declaration");
    UILabel *control = [[UILabel alloc] init];       //no error
    break;

  case button:  //button is an integer constant
    {
      UIButton *control = [[UIButton alloc] init];   //no error
    }
    break;    

  default:
    break;
}

// error when trying to use the *control* variable,
// Use of undeclared identifier 'control'

Is there any way to accomplish this with switch statements?


23rd May 2015: been trying many different approaches to no success.

EDIT: Errors when implementing Cyrille's suggested solution:

UIView *control = nil;
switch (objectType)
{
  case label:  //button is an integer constant
    {
      control = [[UILabel alloc] init];   //no error

      //error: property 'text' not found on object of type 'UIView *'
      control.text = @"Something...";     
    }
    break; 

  default:
    break;
}

Apparently, even after being recast from UIView to UILabel, the object did not inherit all the properties of UILabel, and thus the error:

Property 'text' not found on object of type 'UIView *'

Even Zil's suggestion to prefix the type (UILabel*)[[UILabel alloc]init]; did not work.

Did anyone get this to work?


SOLUTION by DUNCAN C. (see accepted answer below)

    UIView *control = nil;
    switch (objectType)
    {
        case label:  
            control = [[UILabel alloc] init];
            ((UILabel *)control).text = @"Something...";     
            break; 

        default:
            break;
    }

    // UI object instantiated inside switch case recognised!
    ((UILabel *)control).textColor = [UIColor redColor];

Thank you Duncan C.

回答1:

The rules for scope in Objective-C are pretty simple. Any time you enter a pair of curly braces, you enter a new level of scope. Any variable declared inside that scope only exists inside that scope. When you exit the braces, the variable ceases to exist.

The individual cases inside a switch don't need braces, so they don't define a local level of scope, but the entire switch statement does use braces, and so it does define a local scope. Any variable declared inside the switch statement's braces doesn't exist outside the braces.

When you enter a new level of scope you have access to everything declared in your parent scope.

You don't make it completely clear what it is you want to do. I THINK what you want to do is have a variable of a parent class, and then put new objects in it that are from subclasses, and still have access to the unique properties of the child class.

If you declare a variable of a base class, you can assign any object that is a subclass of that type to the variable.

If you want access to the properties of the subclass, through, you have to cast the variable to the appropriate type:

UIView *control = nil;
switch (objectType)
{
  case label:  
    control = [[UILabel alloc] init];   //no error

    ((UILabel *)control).text = @"Something...";     
    break; 
  case button: 
    control = [UIButton buttonWithType: UIButtonTypeSystem];
    [((UIButton *)control) setTitle: "Foo" forState: UIControlStateNormal];
  default:
    break;
}

EDIT

I find the casting syntax to be a pain to type, and it can get confusing. Sometimes it's clearer to create a new temporary variable of the object's actual type, do whatever you need to do, and then assign it to the target variable, like this:

UIView *control = nil;
switch (objectType)
{
  case label:  
    UILabel *aLabel = [[UILabel alloc] init];   //no error

    aLabel.text = @"Something...";   
    aLabel.someOtherProperty = someOtherValue;
    control = aLabel;  
    break; 
  case button: 
    UIButton aButton = [UIButton buttonWithType: UIButtonTypeSystem];
    [aButton setTitle: "Foo" forState: UIControlStateNormal];
    control = aButton
  default:
    break;
}

Outside the switch statement, you won't know which type of object the control variable contains, so you will only be able to access the properties of the UIView base class, unless you cast the variable to the specific type, as shown above.

(Note that the name "control" above is a bad choice, since a label is not a control. Better to use a name like "aView" that implies the common class of all objects that might be in the variable.)



回答2:

Yup, just declare a generic control before your switch:

UIView *control = nil;
switch (objectType) {

  case label:   //label is an integer constant
    NSLog(@"statement before declaration");
    control = [[UILabel alloc] init];       //no error
    break;

  case button:  //button is an integer constant
    {
      control = [[UIButton alloc] init];   //no error
    }
    break; 

  case other:
    control = [[UIView alloc] init]; // works too   

  default:
    break;
}

NSLog(@"Control frame is %@", NSStringFromCGRect(control.frame));


回答3:

Once you declare your control as UIView *, if you try to access properties that doesn't exists on UIView you will get an error.

However as Duncan C saids you can always cast and access to the property using dot notation, example:

((UILabel *)control).text = @"This Works";

and then access the value

NSLog(@"Control text: %@", ((UILabel *)control).text);

I purpose a different approach, declare your control as id and use Objective-C method invocation. You need to make sure that the control responds to the selector that you're trying to invoke. If you want to protect your app for crash when you try to access a selector, you can use respondsToSelector: method. An example of my suggestion:

unsigned objectType = 1;
id control = nil;

switch (objectType) {

    case 1:   //label is an integer constant
        NSLog(@"statement before declaration");
        control = [[UILabel alloc] init];       //no error
        [control setText:@"a"];
        break;

    case 2:  //button is an integer constant
    {
        control = [[UIButton alloc] init];   //no error
    }
        break;

    default:
        break;
}

NSLog(@"Control text %@", [control text]);

And the result is:

2015-05-23 13:12:06.840 test2[46589:12231454] statement before declaration
2015-05-23 13:12:06.841 test2[46589:12231454] Control text `a`

Hope that helps :)



回答4:

The problem is that your control variable is declared inside the scope of the switch statement and then you are trying to use it outside:

UILabel *controlLabel = nil;
UIButton *controlButton = nil;

    /*
    I've defined two variables but you could do the same with one like so
    id control = nil;
    or
    UIControl *control = nil;
    */

switch (objectType) {

  case label:   //label is an integer constant
    NSLog(@"statement before declaration");
    controlLabel = [[UILabel alloc] init];       //no error
    break;

  case button:  //button is an integer constant
    {
      controlButton = [[UIButton alloc] init];   //no error
    }
    break;    

  default:
    break;
}

if (controlLabel) {

} else if (controlButton) {

}