Silver Challenge Problem


#1

I’ve run into a problem while trying to solve the Silver Challenge.

In my method colorChange, the v, representing Hypnosisview isn’t recognized. It is however recognized earlier (see code).

Why is this?

[code]#import “HypnosisViewController.h”
#import “HypnosisView.h”

@implementation HypnosisViewController

  • (void)loadView
    {
    // Creat a view

    CGRect frame = [[UIScreen mainScreen] bounds];
    HypnosisView *v = [[HypnosisView alloc] initWithFrame:frame];

    // Set it as the view of this view controller

    [self setView:v];

    NSArray *segItems = [NSArray arrayWithObjects:@“Red”, @“Green”, @“Blue”, nil];

    UISegmentedControl *segControl = [[UISegmentedControl alloc] initWithItems];

    [segControl setFrame:CGRectMake(60, 340, 200, 26)];

    [v addSubview:segControl];

    // Does not give “undeclared identifier” here

    [v setCircleColor:[UIColor redColor]];

    [segControl addTarget:self
    action:@selector(colorChange)
    forControlEvents:UIControlEventValueChanged];

}

  • (void)colorChange
    {
    // States "undeclared identifier ‘v’

    [v setCirclecolor]
    }

  • (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle
    {

    // Call the superclass’s designated initializer
    self = [super initWithNibName:nil
    bundle:nil];
    if (self) {
    // Get the tab bar itm
    UITabBarItem *tbi = [self tabBarItem];

      //  Give it a label
      [tbi setTitle:@"Hypnosis"];
      
      //   Creat a UIImage from a fil
      //   This will use Hypno@2x.png on retina display devices
      UIImage *i = [UIImage imageNamed:@"Hypno.png"];
      
      //   Put that image on the tab bar item
      [tbi setImage:i];
    

    }

    return self;
    }

  • (void)viewDidLoad
    {
    // Always call the super implementation of viewDidLoad
    [super viewDidLoad];

    NSLog(@“HypnosisViewController loaded its view.”);
    }

@end
[/code]


#2

The colorChange method is treating v as a global variable or an instance variable, which does not exist. Whereas in the loadView method, v does exist as a local variable.

Shouldn’t you need to write the colorChange method like this?

- (void)colorChange
{    
    [[self view] setCirclecolor:[self newColor]]; // I have just made up the newColor method because setCirclecolor: requires an arg
}

#3

Why doesn’t the declaration of v in loadView continue to be in effect for colorChange?


#4

That’s because, local variables are only visible in the scope they exist; they can’t bee seen from the outside.

Even though the loadView method precedes the colorChange in the code, the local variables defined in the loadView are not visible from the outside, so colorChange can’t see them.

In the C Programming Language and its descendants, a pair of braces inside functions create a new scope in which for variables to exist. Scopes can be nested: the inner scopes can see (and maybe even redefine) the variables defined in the outer scopes. A variable defined in the inner scope can not bee seen from a outer scope.

For example:

//  main.m

#import <Foundation/Foundation.h>

void foo ();
int main(int argc, const char * argv[])
{
    foo ();
}

void foo ()
{
    // New scope
    int x;  // x is visible only in this scope - not visible from outside
    x = 32;
    
    if (x > 0)
    {
        // New, inner  scope
        
        assert (x == 32); // the outer x is visible here
        
        // declare a local x - not visible from outside
        int x = 64;
        assert (x == 64); 
    }
    assert (x == 32); 
    
    void bar ();
    bar ();
}

void bar ()
{
    // New scope
    x = 0;  // error: x does not exist in this scope
    
    // declare a local x
    int x = 512;
    {
        // New, inner scope
        
        assert (x == 512); // the outer x is visible here
        
        // declare a local x - not visible from outside
        int x = 1024;
        assert (x == 1024);
        
        // declare a local y - not visible from outside
        int y = 2048;
    }
    
    assert (y == 2048); // error: inner y is not visible from here
}

#5

I now understand the global and local variables, thank you.

I have made the following changes but now get the error message as indicated in my comment section.

There seems to be now way to access anything from the loadView method, I have seen some other posts which use tags but I don’t want to go this route. I thought about re-declaring the CGRect and v in colorChange but I don’t think this would work either.

colorChange does not seem to recognize CircleColor, presumably because it is in HypnosisView *v (instantiated in loadView).

What am I doing wrong?

[code]#import “HypnosisViewController.h”
#import “HypnosisView.h”

@implementation HypnosisViewController

  • (void)loadView
    {
    // Creat a view

    CGRect frame = [[UIScreen mainScreen] bounds];
    HypnosisView *v = [[HypnosisView alloc] initWithFrame:frame];

    // Set it as the view of this view controller

    [self setView:v];

    NSArray *segItems = [NSArray arrayWithObjects:@“Red”, @“Green”, @“Blue”, nil];

    UISegmentedControl *segControl = [[UISegmentedControl alloc] initWithItems];

    [segControl setFrame:CGRectMake(60, 340, 200, 26)];

    [v addSubview:segControl];

    [v setCircleColor:[UIColor redColor]];

    [segControl addTarget:self
    action:@selector(colorChange)
    forControlEvents:UIControlEventValueChanged];

}

  • (void)colorChange
    {

    // No visible @interface for ‘UIView’ declares the selector ‘setCircleColor.’
    [[self view] setCircleColor:[UIColor redColor]];
    }
    [/code]


#6

You are nearly there.

Try this:

- (void)colorChange
{
    if ([[self view] isMemberOfClass:[HypnosisView class]])
    {
        HypnosisView *hypnosisView = (HypnosisView *) [self view];
        [hypnosisView setCircleColor:[UIColor redColor]];
    }
}

The if statement should always execute because loadView is setting the ViewController’s view to an instance of HypnosisView.


#7

I made the changes, but get an error as indicated below, it seems the setCircleColor is still not recognized despite the changes.

[code]#import “HypnosisViewController.h”
#import “HypnosisView.h”

@implementation HypnosisViewController

  • (void)loadView
    {
    // Creat a view

    CGRect frame = [[UIScreen mainScreen] bounds];
    HypnosisView *v = [[HypnosisView alloc] initWithFrame:frame];

    // Set it as the view of this view controller

    [self setView:v];

    NSArray *segItems = [NSArray arrayWithObjects:@“Red”, @“Green”, @“Blue”, nil];

    UISegmentedControl *segControl = [[UISegmentedControl alloc] initWithItems];

    [segControl setFrame:CGRectMake(((320 / 2 - 200 / 2)), 340, 200, 26)];

    [v addSubview:segControl];

    [v setCircleColor:[UIColor redColor]];

    [segControl addTarget:self
    action:@selector(colorChange)
    forControlEvents:UIControlEventValueChanged];

}

  • (void)colorChange
    {

    if ([[self view] isMemberOfClass:[HypnosisView class]])
    {
    HypnosisView *hypnosisView = (HypnosisView *) [self view];

      //  No know class method for selector 'setCircleColor:'
      [HypnosisView setCircleColor:[UIColor redColor]];
    

    }[/code]


#8

You have a case error: HypnosisView should be hypnosisView.


#9

I made it through one problem, only to have another. My program builds without errors and runs as well until I click on the segment control. Then the whole thing crashes, the console displays "Unrecognized selector sent to instance…

I suspect my problem is related to my issue earlier, that is trying to access objects or variables outside of the scope in which they are declared. I tried to make segControl accessible from within colorChange, perhaps I didn’t do it correctly.

[code]#import “HypnosisViewController.h”
#import “HypnosisView.h”

@implementation HypnosisViewController

  • (void)loadView
    {
    // Creat a view

    CGRect frame = [[UIScreen mainScreen] bounds];
    HypnosisView *v = [[HypnosisView alloc] initWithFrame:frame];

    // Set it as the view of this view controller

    [self setView:v];

    NSArray *segItems = [NSArray arrayWithObjects:@“Red”, @“Green”, @“Blue”, nil];

    UISegmentedControl *segControl = [[UISegmentedControl alloc] initWithItems];

    [segControl setFrame:CGRectMake(((320 / 2 - 200 / 2)), 340, 200, 26)];

    [v addSubview:segControl];

    [v setCircleColor:[UIColor redColor]];

    [segControl addTarget:self
    action:@selector(colorChange)
    forControlEvents:UIControlEventValueChanged];

}

  • (void)colorChange
    {

    if ([[self view] isMemberOfClass:[HypnosisView class]])
    {
    HypnosisView *hypnosisView = (HypnosisView *) [self view];

      UISegmentedControl *sc = (UISegmentedControl *)[self view];
      
      NSInteger i = [sc selectedSegmentIndex];
      
      if (i == 0){
          [hypnosisView setCircleColor:[UIColor redColor]];
      }
      
      if (i == 1){
          [hypnosisView setCircleColor:[UIColor greenColor]];
      }
      
      if (i == 2){
          [hypnosisView setCircleColor:[UIColor blueColor]];
      }
    

    }
    }[/code]


#10

You have “careered across the road and gone through a hedge” here:

- (void)colorChange
{
    if ([[self view] isMemberOfClass:[HypnosisView class]])
    {
        HypnosisView *hypnosisView = (HypnosisView *) [self view];
        UISegmentedControl *sc = (UISegmentedControl *)[self view];   // ???
     ...
    }
    ...

How can you get a UISegmentedControl instance from HypnosisViewController’s view?

You should get out of try-anything-out-of-desperation mode and focus on understanding the fundamentals.

Relax, take a break, and find out what "[color=#FF0000]Unrecognized selector [/color] actually means and what is causing it.


#11

I figured it was goofy but I was trying to “grab” the selectedsegIndex which is readily available in loadView

NSInteger i = [segControl selectedSegmentIndex];

The trick is how to get the “i” value accessible in colorChange.


#12

Still working on it, since the variables in each method are not available to each other, I tried a work around, and wrote the selectedSegindex to a plist file which I planned on retrieving in the colorChange method. I had the console log the value of the selectedSegmentindex so I could monitor what’s going on.

The scv value is null regardless of which button is pushed meaning I’m not retrieving the value correctly. The colorChange method needs to be accessed directly. The program runs well, always changing the circle to green for obvious reasons.

[code]#import “HypnosisViewController.h”
#import “HypnosisView.h”

@implementation HypnosisViewController

  • (void)loadView
    {
    // Creat a view

    CGRect frame = [[UIScreen mainScreen] bounds];
    HypnosisView *v = [[HypnosisView alloc] initWithFrame:frame];

    // Set it as the view of this view controller

    [self setView:v];

    NSArray *segItems = [NSArray arrayWithObjects:@“Red”, @“Green”, @“Blue”, nil];

    UISegmentedControl *segControl = [[UISegmentedControl alloc] initWithItems];

    [segControl setFrame:CGRectMake(((320 / 2 - 200 / 2)), 340, 200, 26)];

    [v addSubview:segControl];

    [v setCircleColor:[UIColor redColor]];

    [segControl addTarget:self
    action:@selector(colorChange)
    forControlEvents:UIControlEventValueChanged];

    NSLog(@“segControl %d”, [segControl selectedSegmentIndex]);

    NSInteger i = [segControl selectedSegmentIndex];

    NSNumber *sci = [[NSNumber alloc]initWithInt:(i)];

    NSArray *sco = [NSArray arrayWithObjects:sci, nil];

    [sco writeToFile:(@"/tmp/sco.plist")
    atomically:YES];

    }

  • (void)colorChange
    {

    if ([[self view] isMemberOfClass:[HypnosisView class]])
    {
    HypnosisView *hypnosisView = (HypnosisView *) [self view];

      NSArray *scv = [NSArray arrayWithContentsOfFile:@"/tmp.sco.plist"];
      
      NSLog(@"scv value %@", scv);
      
      int i = 1;
      
     if (i == 0){
          [hypnosisView setCircleColor:[UIColor redColor]];
      }
      
      if (i == 1){
          [hypnosisView setCircleColor:[UIColor greenColor]];
      }
      
      if (i == 2){
          [hypnosisView setCircleColor:[UIColor blueColor]];
      }
    

    }

}[/code]


#13

I still haven’t figured this out despite re-reading earlier chapters, I would appreciate any help,
Thanks,


#14

I finally got it,

I declared UISegmentedControl *segControl in the header file and then wrote this:

segControl = [[UISegmentedControl alloc] initWithItems];

instead of this:

UISegmentedControl *segControl = [[UISegmentedControl alloc]initWithItems];

The second method “hides” the instance variables, whereas the first allows the segControl to have its selectedSegmentindex accessible in colorChange. This seems simple but for me is quite a breakthrough.