Silver Challenge Solution


#1

[code]//
// HypnosisViewController.m
// HypnoTime
//
// Created by Roy Law on 13-5-30.
// Copyright © 2013年 Roy Law. All rights reserved.
//

#import “HypnosisViewController.h”
#import “HypnosisterView.h”

@implementation HypnosisViewController
{
HypnosisterView *aHView;
}

-(void)loadView
{
CGRect frame = [[UIScreen mainScreen] bounds];
aHView = [[HypnosisterView alloc] initWithFrame:frame];

[self setView:aHView];

NSArray *array = [[NSArray alloc] initWithObjects:@"Red", @"Green", @"Blue", nil];
UISegmentedControl *colorSegCtrl = [[UISegmentedControl alloc] initWithItems:array];
[colorSegCtrl setFrame:CGRectMake(33, 350, 255, 44)];
[aHView addSubview:colorSegCtrl];

[colorSegCtrl addTarget:self action:@selector(changeColor:) forControlEvents:UIControlEventValueChanged];

}

-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nil bundle:nil];
if (self) {
self.tabBarItem.title = @“Hypnosis”;
self.tabBarItem.image = [UIImage imageNamed:@“Hypno.png”];
}
return self;
}

-(void)changeColor:(id)sender
{
switch ([sender selectedSegmentIndex]) {
case 0:
NSLog(@“red”);
[aHView setCircleColor:[UIColor redColor]];
break;
case 1:
NSLog(@“green”);
[aHView setCircleColor:[UIColor greenColor]];
break;
case 2:
NSLog(@“blue”);
[aHView setCircleColor:[UIColor blueColor]];
break;
default:
break;
}
}

@end
[/code]

[code]//
// HypnosisterView.h
// Hypnosister
//
// Created by Roy Law on 13-5-29.
// Copyright © 2013年 Roy Law. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface HypnosisterView : UIView

@property (strong,nonatomic) UIColor *circleColor;

@end
[/code]

[code]//
// HypnosisterView.m
// Hypnosister
//
// Created by Roy Law on 13-5-29.
// Copyright © 2013年 Roy Law. All rights reserved.
//

#import “HypnosisterView.h”

@implementation HypnosisterView

@synthesize circleColor;

-(void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect bounds = self.bounds;

CGPoint aCenter;
aCenter.x = bounds.origin.x + bounds.size.width/2.0;
aCenter.y = bounds.origin.y + bounds.size.height/2.0;

float maxRadius = hypot(aCenter.x, aCenter.y)/2.0;

CGContextSetLineWidth(ctx, 5);

// CGContextSetRGBStrokeColor(ctx, 0.6, 0.6, 0.6, 1.0);
// [[UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1.0] setStroke];
[self.circleColor setStroke];
for (float currentRadius = maxRadius; currentRadius>0; currentRadius-=10) {
CGContextAddArc(ctx, aCenter.x, aCenter.y, currentRadius, 0.0, M_PI*2.0, YES);
CGContextStrokePath(ctx);
}

// CGContextSaveGState(ctx);

NSString *text = @"You are getting sleepy.";
UIFont *font = [UIFont boldSystemFontOfSize:28];

CGRect textRect;
textRect.size = [text sizeWithFont:font];
textRect.origin.x = aCenter.x - textRect.size.width/2.0;
textRect.origin.y = aCenter.y - textRect.size.height/2.0;

[[UIColor blackColor] setFill];

CGSize offset = CGSizeMake(4, 3);
CGColorRef color = [[UIColor grayColor] CGColor];

CGContextSetShadowWithColor(ctx, offset, 2.0, color);

[text drawInRect:textRect withFont:font];
/*
CGContextRestoreGState(ctx);

[[UIColor blueColor] setStroke];
CGContextMoveToPoint(ctx, aCenter.x, aCenter.y-100);
CGContextAddLineToPoint(ctx, aCenter.x, aCenter.y+100);
CGContextStrokePath(ctx);
CGContextMoveToPoint(ctx, aCenter.x-100, aCenter.y);
CGContextAddLineToPoint(ctx, aCenter.x+100, aCenter.y);
CGContextStrokePath(ctx);    

*/
}

-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setBackgroundColor:[UIColor clearColor]];
self.circleColor = [UIColor lightGrayColor];
}
return self;
}

-(BOOL)canBecomeFirstResponder
{
return YES;
}

-(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
NSLog(@“Device is shaking”);
[self setCircleColor:[UIColor redColor]];
}

-(void)setCircleColor:(UIColor *)cColor
{
circleColor = cColor;
[self setNeedsDisplay];
}
@end
[/code]


#2

I have repeated this code exactly and keep getting an exception thrown when I run (build is fine).
In particular I am getting an unrecognized selector for [HypnosisViewController changeColor].
I have declared -(void) changeColor: (id) sender in my .h and defined it in my .m

Any thoughts?

I have included my code in case I have a crazy typo someone can catch :slight_smile:

Thanks for your help!

HypnosisViewController.h

[code]#import <Foundation/Foundation.h>

@interface HypnosisViewController : UIViewController

  • (void) changeColor: (id)sender;

@end
[/code]

HypnosisViewController.m

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

@implementation HypnosisViewController
{
HypnosisView *v;
}

  • (id) initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle
    {
    self = [super initWithNibName:nil bundle:nil];
    if (self)
    {
    // Get the tab bar item
    UITabBarItem *tbi = [self tabBarItem];

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

    }

    return self;
    }

  • (void) loadView
    {
    // Create a view
    CGRect frame = [[UIScreen mainScreen]bounds];
    v = [[HypnosisView alloc]initWithFrame:frame];

    // Set it as the view of this view controller
    [self setView:v];

    NSArray *colorArray = [[NSArray alloc]initWithObjects: @“Red”, @“Blue”, @“Green”, nil];
    UISegmentedControl *uiSegments = [[UISegmentedControl alloc]initWithItems:colorArray];
    [uiSegments setFrame:CGRectMake(33, 350, 255, 44)];
    [v addSubview:uiSegments];
    [uiSegments addTarget:self
    action:@selector(changeColor)
    forControlEvents:UIControlEventValueChanged];
    }

  • (void) changeColor:(id)sender
    {
    switch ([sender selectedSegmentIndex])
    {
    case 0:
    NSLog(@“Red”);
    [v setCircleColor:[UIColor redColor]];
    break;

      case 1:
          NSLog(@"Blue");
          [v setCircleColor: [UIColor blueColor]];
          break;
          
      case 2:
          NSLog(@"Green");
          [v setCircleColor: [UIColor greenColor]];
          break;
          
      default:
          break;
    

    }
    }

  • (void) viewDidLoad
    {
    [super viewDidLoad];

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

}

@end
[/code]

HypnosisView.h

[code]#import <Foundation/Foundation.h>

@interface HypnosisView : UIView

@property (nonatomic, strong) UIColor *circleColor;

@end
[/code]

HypnosisView.m

#import "HypnosisView.h"

@implementation HypnosisView

@synthesize circleColor;


// Designated initializer
- (id) initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self)
    {
        // All HypnosisViews start with a clear background color
        [self setBackgroundColor:[UIColor clearColor]];
        [self setCircleColor:[UIColor lightGrayColor]];
    }
    return self;
}


- (BOOL)canBecomeFirstResponder
{
    return YES;
}

-(void) drawRect:(CGRect)dirtyRect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGRect bounds = [self bounds];
    
    // Figure out the center of the rectangle
    CGPoint center;
    center.x = bounds.origin.x + bounds.size.width / 2.0;
    center.y = bounds.origin.y + bounds.size.height / 2.0;
    
    // The radius of the circle should be nearly as big as the view
    // hypot = hypotenuse
    float maxRadius = hypot(bounds.size.width, bounds.size.height) / 2.0;
    
    // The thickness of the line should be 10 points wide
    CGContextSetLineWidth(ctx, 10);
    
    [self.circleColor setStroke];
    
    
    // Draw concentric circles from the outside in
    for (float currentRadius = maxRadius; currentRadius >0; currentRadius -= 20)
    {
        // Add a path to the context
        CGContextAddArc(ctx, center.x, center.y, currentRadius, 0.0, M_PI * 2.0, YES);
        
        // Perform drawing instruction; removes path
        CGContextStrokePath(ctx);
        
    }
    
    // Create a string
    NSString *text = @"You are getting sleepy.";
    
    // Get a font to draw it in
    UIFont *font = [UIFont boldSystemFontOfSize:28];
    
    CGRect textRect;
    
    // How big is this string when drawn in this font?
    textRect.size = [text sizeWithFont:font];
    
    // Let's put the string in the center if the view
    textRect.origin.x = center.x - textRect.size.width / 2.0;
    textRect.origin.y = center.y - textRect.size.height / 2.0;
    
    // Set the fill color of the current contect to black
    [[UIColor blackColor] setFill];
    
    
    // The shadow will move 4 points to the right and 3 points down from the text
    CGSize offset = CGSizeMake(-4,3);
    
    // The shadow will be gray in color
    CGColorRef color = [[UIColor darkGrayColor] CGColor];
    
    // Set the shadow of the context with these parameters
    // All subsequent drawing will be shadowed
    CGContextSetShadowWithColor(ctx, offset, 2.0, color);
    
    // Draw the string
    [text drawInRect:textRect withFont:font];
    
}

- (void) motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
    if (motion == UIEventSubtypeMotionShake)
    {
    NSLog(@"Device started shaking!");
    [self setCircleColor:[UIColor redColor]];
    } 
}

- (void) setCircleColor:(UIColor *)clr
{
    circleColor = clr;
    [self setNeedsDisplay];
}

@end

#3

I fixed the problem.

In case anyone was wondering - 9I was missing a colon in @selector(changeColor:).


#4

My instinct in solving this problem is to abandon programmatically setting the views in the VC, since we now have more than 1 view element.

Make a XIB, set File’s Owner to HypnosisViewController, add a UIView to the XIB (change class to HypnosisView in Identity), add a SegmentedControl. Get rid of loadView. Override awakeFromNib in HypnosisView and do the init setup there.

You can use a weak IBOutlet property to connect the VC with the HypnosisView, and change color from the IBAction button.

Just not interested in placing multiple screen elements programmatically. To me it’s easier to just do it graphically.


#5

jgelling - can you please give more details. I’m stuck on this part. I did take loadView out, and set the XIB File’s Owner to HypnosisViewController. I also connected the view outlet.

But Setting the UIView class to HypnosisView in the Identity inspector leaves me with a runtime crash … NSInvalidArgumentException reason: 'NSConcreteMutableAttributedString initWithString:: nil value … in the drawRect: method of my HypnosisView.

Does the HypnosisView initWithFrame: go away altogether and get replaced with awakeFromNib?

Thanks.


#6

So your XIB will have 2 views: the HypnosisView and the Segmented Control.

The HypnosisView is a custom view we’ve made programmatically. Before we instantiated it from loadView - but if we do that then we have to setup everything programmatically. And programmatically doing layout sucks.

Now we want to instantiate it from a XIB, so the XIB can handle the graphical layout. You make the XIB and set the File’s Owner to the ViewController and make the connections.

awakeFromNib is called on all the objects in the XIB and it’s a good place to finish set-up. Add this to the HypnosisView:

-(void)awakeFromNib { [self setBackgroundColor:[UIColor clearColor]]; [self setCircleColor:[UIColor lightGrayColor]]; [self becomeFirstResponder]; }

As long as that code is in the UIView (HypnosisView) and you’ve made the proper connections/identity changes, it should run fine.


#7

Got it. awakeFromNib was what I was missing. Thanks!