Bronze & Silver solution... Gold is tricky!


#1

So in this chapter, the Bronze challenge was to change the circles to different colours (ok, “colors” for you Americans!). As a number of other posters have done, I set up an array of colours, and then I used a randomization to pick which colour any given circle would be drawn in:
hypnosisview.h

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

@interface HypnosisView : UIView

@property (nonatomic, strong) UIColor * circleColor;

@end[/code]
hynosisview.m

[code]#import “HypnosisView.h”

@implementation HypnosisView

@synthesize circleColor;

  • (id)initWithFrame:(CGRect)frame
    {
    self = [super initWithFrame:frame];
    if (self) {
    // Initialization code
    [self setBackgroundColor:[UIColor clearColor]];
    [self setCircleColor:[UIColor lightGrayColor]];
    }
    return self;
    }

  • (void)drawRect:(CGRect)dirtyRect
    {
    // Set up an array of colors to draw the different circles with.
    NSArray * presetComponentColors= [[NSArray alloc] initWithObjects:[UIColor blackColor], [UIColor darkGrayColor], [UIColor lightGrayColor], [UIColor grayColor], [UIColor redColor], [UIColor greenColor], [UIColor blueColor], [UIColor cyanColor], [UIColor yellowColor], [UIColor magentaColor], [UIColor orangeColor], [UIColor purpleColor], [UIColor brownColor], nil];
    // Drawing code
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGRect bounds = [self bounds];

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

    float maxRadius = hypot(bounds.size.width, bounds.size.height) / 2.0;

    CGContextSetLineWidth(ctx, 10);

    [[self circleColor] setStroke];

    // Draw concentric circles
    int colorsCount = [presetComponentColors count];
    // remember that an array goes from 0…num,
    // but since we’re using the arc4random() function, don’t need to subtract 1 here

    UIColor * currentColor;
    for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) {
    // Add a path to the context.
    // The arc4random(num) function produces a uniform random set
    // from 0 to (num -1).
    currentColor = [presetComponentColors objectAtIndex:arc4random_uniform(colorsCount)];
    [self setCircleColor:currentColor];
    [[self circleColor] setStroke];

      CGContextAddArc(ctx, center.x, center.y, currentRadius, 0.0, M_PI*2.0, YES);
      
      // Perform drawing
      CGContextStrokePath(ctx);
    

    }

    // Create the getting sleepy text.
    NSString * text = @“You are getting sleepy”;
    UIFont * font = [UIFont boldSystemFontOfSize:28];

    CGRect textRect;
    textRect.size = [text sizeWithFont:font];

    textRect.origin.x = center.x - textRect.size.width/2.0;
    textRect.origin.y = center.y +40 - textRect.size.height/2.0;

    [[UIColor blackColor] setFill];

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

    CGContextSetShadowWithColor(ctx, offset, 2.0, color);

    [text drawInRect:textRect withFont:font];
    CGContextSetShadowWithColor(ctx,offset,1.0,NULL); // turn off the shadow

    // call a new method to draw the cross-hairs
    [self drawCrossHairs:ctx atPoint:center];
    }

-(BOOL)canBecomeFirstResponder {
return YES;
}

-(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];
}

-(void)drawCrossHairs:(CGContextRef)ctx atPoint:(CGPoint)center {
// For the Silver Challenge of this chapter, create a new
// method to draw the required green crosshairs.
// Make them “fancy” with a circle around the centre-point, and the
// centre-point itself not drawn.
double sizeOfCrossHair=45.0;

CGContextSaveGState(ctx);
CGContextSetLineWidth(ctx,2);

CGContextBeginPath(ctx);
CGContextSetRGBStrokeColor(ctx,0.0,1.0,0.0,1.0); // i.e. green

// Horizontal line of the crosshair
CGContextMoveToPoint(ctx,center.x-sizeOfCrossHair,center.y);
CGContextAddLineToPoint(ctx,center.x-sizeOfCrossHair/6,center.y);
CGContextMoveToPoint(ctx,center.x+sizeOfCrossHair/6,center.y);
CGContextAddLineToPoint(ctx,center.x+sizeOfCrossHair,center.y);

// Vertical line of the crosshair
CGContextMoveToPoint(ctx,center.x,center.y-sizeOfCrossHair);
CGContextAddLineToPoint(ctx,center.x,center.y-sizeOfCrossHair/6);
CGContextMoveToPoint(ctx,center.x,center.y+sizeOfCrossHair/6);
CGContextAddLineToPoint(ctx,center.x,center.y+sizeOfCrossHair);

// Draw a circle at 80% of the radius
[self setCircleColor:[UIColor greenColor]];
[[self circleColor] setStroke];

// First move to the start of the circle, which is at x=1, y=0
CGContextMoveToPoint(ctx,center.x+0.8*sizeOfCrossHair,center.y);
CGContextAddArc(ctx, center.x, center.y, 0.8*sizeOfCrossHair, 0.0, M_PI*2.0, YES);

CGContextStrokePath(ctx);
// Restore the GState
CGContextRestoreGState(ctx);

}

@end[/code]

And then, to solve the Silver challenge, I added a method to draw crosshairs… drawCrossHairs().

For the Gold challenge, I am able to get the Big Nerd Ranch logo image to display on top, in a new view. I’m even able to get it to clip to a circle… but the gradient fill seems to be a doozy (which I haven’t yet solved). I created a new class BigNerdLogoView, with the .h file as follows:
bignerdlogoview.h

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

@interface BigNerdLogoView : UIView

@end[/code]
i.e. nothing to it. And the .m file:
bignerdlogoview.m

[code]#import “BigNerdLogoView.h”

@implementation BigNerdLogoView

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

  • (void)drawRect:(CGRect)rect
    {
    UIImage *bigNerdLogoImage = [UIImage imageNamed:@“bignerdlogo.png”];
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGRect bounds = [self bounds];

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

    float logoRadius = hypot(bounds.size.width, bounds.size.height) / 3.0;

    CGContextSetLineWidth(ctx, 1);

    CGSize offset = CGSizeMake(1,1);
    CGColorRef color = [[UIColor darkGrayColor] CGColor];
    CGContextSetShadowWithColor(ctx, offset, 3.0, color);

    CGContextAddArc(ctx, center.x, center.y, logoRadius, 0.0, M_PI*2.0, YES);
    CGContextClip(ctx);
    CGContextSetGrayFillColor(ctx, 0.9, 1.0);
    CGContextFillEllipseInRect(ctx, bounds);
    CGContextSetLineWidth(ctx, 3);

    // draw the Big Nerd Ranch logo, but the CGContextClip function
    // from a few lines before will constrain it to the circle.
    [bigNerdLogoImage drawInRect:bounds blendMode:kCGBlendModeScreen alpha:1.0];
    CGContextSetGrayStrokeColor(ctx, 0.0, 1.0);
    CGContextAddEllipseInRect(ctx, bounds);
    // Perform drawing
    CGContextStrokePath(ctx);

}

@end[/code]

You also have to add the bignerdlogoview.h file to the import lines at the top of the HypnosisterAppDelegate.h file:

[code]#import <UIKit/UIKit.h>
#import “HypnosisView.h”
#import “BigNerdLogoView.h”

@interface HypnosisterAppDelegate : UIResponder <UIApplicationDelegate, UIScrollViewDelegate>
{
HypnosisView *view;
BigNerdLogoView *subView;
}

@property (strong, nonatomic) UIWindow *window;

@end
[/code]

Anyway, I’d love to see if anyone else has solved the gradient-fill part of the Gold Challenge in this chapter. Or if anyone from BNR can give any pointers on it?


#2

Hi ryden,
I can’t see you working with CGGradientRef or CGColorSpaceRef. You need them to get the gradient.

Here’s my solution:

NerdLogoView.h:

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

@interface NerdLogoView : UIView {
CGRect logoFrame;
}

@end[/code]

NerdLogoView.m:

[code]
#import “NerdLogoView.h”

@implementation NerdLogoView

  • (id) initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
    [self setBackgroundColor:[UIColor clearColor]];
    logoFrame = frame;
    }

    return self;

}

  • (void) drawRect:(CGRect)rect {
    UIImage *nerdLogo = [UIImage alloc];
    nerdLogo = [UIImage imageNamed:@“Icon@2x.png”];
    CGRect logoRect = CGRectMake(rect.origin.x+2, rect.origin.y+2, logoFrame.size.width-7, logoFrame.size.height-7);

    CGContextRef ctx = UIGraphicsGetCurrentContext();

    //Create Shadows
    CGContextSaveGState(ctx);
    CGContextAddEllipseInRect(ctx, logoRect);
    CGContextSetRGBFillColor(ctx, 0, 1, 0, 1);
    CGSize offset = CGSizeMake(logoFrame.size.width/17, logoFrame.size.height/17);
    CGColorRef color = [[UIColor darkGrayColor] CGColor];
    CGContextSetShadowWithColor(ctx, offset, 2.0, color);
    CGContextFillPath(ctx);
    CGContextRestoreGState(ctx);

    //Draw black frame
    CGContextSaveGState(ctx);
    [[UIColor blackColor] setStroke];
    CGContextSetLineWidth(ctx, 3);
    CGContextStrokeEllipseInRect(ctx, logoRect);
    CGContextRestoreGState(ctx);

    //Draw logo in circle
    CGContextSaveGState(ctx);
    CGContextAddEllipseInRect(ctx, logoRect);
    CGContextClip(ctx);
    [nerdLogo drawInRect];
    CGContextRestoreGState(ctx);

    //Add gradient
    CGContextSaveGState(ctx);
    CGContextAddEllipseInRect(ctx, logoRect);
    CGContextClip(ctx);

    CGColorSpaceRef csRef = CGColorSpaceCreateDeviceRGB();
    CGColorRef colors[] = { [[UIColor blueColor] CGColor] , [[UIColor clearColor] CGColor] };
    CFArrayRef cfColors = CFArrayCreate(kCFAllocatorDefault, (const void**)colors, sizeof(colors) / sizeof(CGColorRef), &kCFTypeArrayCallBacks);
    CGGradientRef gRef = CGGradientCreateWithColors(csRef, cfColors, NULL);

    CGPoint startPoint = CGPointMake(logoRect.size.width/2, -logoRect.size.height/2);
    CGPoint endPoint = CGPointMake(logoRect.size.width/2, logoRect.size.height/2);
    CGContextDrawLinearGradient(ctx, gRef, startPoint, endPoint, 0);

    CGContextRestoreGState(ctx);

    //Show borders
    BOOL enableGrid = NO;

    if (enableGrid) {
    CGContextSaveGState(ctx);
    CGContextSetRGBStrokeColor(ctx, 0, 1, 0, 1);
    CGContextSetLineWidth(ctx, 1);
    CGContextMoveToPoint(ctx, logoFrame.size.width, 0);
    CGContextAddLineToPoint(ctx, logoFrame.size.width, logoFrame.size.height);
    CGContextMoveToPoint(ctx, 0, logoFrame.size.height);
    CGContextAddLineToPoint(ctx, logoFrame.size.width, logoFrame.size.height);
    CGContextStrokePath(ctx);
    CGContextRestoreGState(ctx);
    }

}

@end[/code]

The enableGrid boolean can show you where your space ends (Only two lines are implemented, because the logo should always be at the top left corner of your screen).

Then you can add it to you AppDelegate.m, after you imported the header of NerdLogoView:

//After "view = [[HypnosisView alloc] initWithFrame:bigRect];"
    NerdLogoView *nerdLogo = [[NerdLogoView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)]; //Feel free to make the Rect a little bigger or place it somewhere else
    [view addSubview];

That’s it. Hope this helped you out.

Greetings from Germany
Joerg

PS: Sorry for any grammar mistakes that may have occurred - I’m not a native speaker.