Answers for the Gold Challenge?


#1

Sorry if I missed where this is but I got completely stuck on the Gold Challenge [dropShadow and gradient] and want to see the way the author did it, but I can’t find that code anywhere. I see that there is some pseudo-code as a guideline but I can’t for the life of me figure out how to implement it. It’s not in the solutions zip file for Chapter 4. I also checked the code for Chapter 5 thinking maybe it was in that but no luck.
I’m hoping that the answers are somewhere, it makes no sense to give a challenge but then not have the solution for how it could be done, seems odd to me.


#2

The following guides are worth reading (from cover to cover) to supplement your understanding of the material in the book:


#3

I’m sure those links are helpful but there really is no point in adding these challenges to each chapter if the user can’t check their work against the way the author had envisioned the code. I’m sure you can say something about ‘well if it’s the result the author was looking for, that’s the answer’, but part of the reason why a lot of people buy books like this is for more of a best-practices point of view. To see how someone with experience in the field would solve that challenge.


#4

Here’s my CHALLENGE solutions (both BRONZE and GOLD). Don’t look at the code if you want to figure it out on your own.

This solution worked for me and displays my image and the triangle on any size iPhone screen. I appreciate any feedback on something that I could have done better, or to correct something that may cause an issue down the road (like a memory leak, etc…)

This code appears right after the line of code to draw the circles: [path stroke];

Thanks, —> Kelsey McClanahan

[code]
// ========================================================================
// Get the current graphics context
CGContextRef currentContext = UIGraphicsGetCurrentContext();

// ========================================================================
// Draw Triangle with Gradient Color)
//

// Save the state of the current graphics context
CGContextSaveGState(currentContext);

// Create a triangle path
UIBezierPath *trianglePath = [[UIBezierPath alloc] init];
[trianglePath moveToPoint:CGPointMake(bounds.size.width / 2.0, 75.0)];
[trianglePath addLineToPoint:CGPointMake(50.0, bounds.size.height - 75.0)];
[trianglePath addLineToPoint:CGPointMake(bounds.size.width - 50.0, bounds.size.height - 75.0)];
[trianglePath addLineToPoint:CGPointMake(bounds.size.width / 2.0, 75.0)];
// Draw the path -- not required for the challenge but I wanted the outline of it
[trianglePath stroke];
// Clip the drawing area to just the defined triangle
[trianglePath addClip];

// Define and create the gradient
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 1.0, 1.0, 0.0, 1.0,
                            0.0, 1.0, 0.0, 1.0 };
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorspace, components, locations, 2);

// Draw the gradient
CGPoint startPoint = CGPointMake(0.0, 75.0);
CGPoint endPoint = CGPointMake(0.0, bounds.size.height - 75.0);
CGContextDrawLinearGradient(currentContext, gradient, startPoint, endPoint, 0);
// Release CG "objects"
CGGradientRelease(gradient);
CGColorSpaceRelease(colorspace);

// Restore the state of the graphics context
CGContextRestoreGState(currentContext);


// ========================================================================
// Draw Graphic image with Shadow
// NOTE: The image is a simple PNG of my name in blue text
//

// Save the state of the current graphics context
CGContextSaveGState(currentContext);

// Grab the image that was added to the Xcode project
UIImage *kelseyImage = [UIImage imageNamed:@"Kelsey.png"];

// Define the display rectangle for the image
// NOTE: I made it too large for the display, that's why I had to use a different size than
//       the original image size.  Also, I wanted it to display centered on the screen,
//       regardless of the display size (3.5" or 4" Retina display)
CGRect kelseyRect = CGRectMake((bounds.size.width - (kelseyImage.size.width / 1.5)) / 2.0,
                               (bounds.size.height - (kelseyImage.size.height / 1.5)) / 2.0,
                               kelseyImage.size.width / 1.5,
                               kelseyImage.size.height / 1.5);

// Turn on the shadow effect before the image is drawn
CGContextSetShadowWithColor(currentContext, CGSizeMake(4,6), 4, [UIColor darkGrayColor].CGColor);
// Draw the image -- it will have a dark gray shadow under it
[kelseyImage drawInRect:kelseyRect];

// Restore the state of the graphics context
CGContextRestoreGState(currentContext);[/code]

#5

Hi Kelsey, thanks for posting your solution. But I’m having troubles with the opacity of the logo, in this respect I miss your solution of the bronze challenge.

When I add the logo as indicated in the book it just draws the logo on top of the circles, so you end up with a white square on top of the circles. I have been playing around with the opacity settings of a UIImageView (alpha property setting), but I can’t get it as is shown in the book.

Any thoughts?

Regards, Frank


#6

Make sure your image has a transparent background and is in a format such as PNG that will draw properly.


#7

I would like to share my approach to this challenge.

// Get the currentContext value inside drawRect:
CGContextRef currentContext = UIGraphicsGetCurrentContext();

// Generate each triangle path points
// I'm using the variable center to create the triangle points
CGPoint triangleStartPoint;
triangleStartPoint.x = center.x;
triangleStartPoint.y = center.y - 130;    
CGPoint triangleSecondPoint;
triangleSecondPoint.x = center.x + 90;
triangleSecondPoint.y = center.y + 130;    
CGPoint triangleThirdPoint;
triangleThirdPoint.x = center.x - 90;
triangleThirdPoint.y = center.y + 130;

// Glue the points with a UIBezierPath instance
UIBezierPath *trianglePath = [[UIBezierPath alloc] init];
[trianglePath moveToPoint:triangleStartPoint];
[trianglePath addLineToPoint:triangleSecondPoint];
[trianglePath addLineToPoint:triangleThirdPoint];
[trianglePath closePath];

// Save the state of the current graphics context
CGContextSaveGState(currentContext);
    
// Draw the triangle!
[trianglePath addClip];
    
// Setting the gradient constants
// I choose to use a beautiful blue gradient inspired in iOS color
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 0.114, 0.471, 0.937, 1.0,    // Start color
                          0.506, 0.953, 0.992, 1.0 };  // End color
    
// Set the color space for gradient application
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, 2);
    
CGPoint startPoint; // Gradient start point
startPoint.x = center.x;
startPoint.y = center.y - 130.0;
CGPoint endPoint;   // Gradient end point
endPoint.x = center.x;
endPoint.y = center.y + 130.0;
    
CGContextDrawLinearGradient(currentContext, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
    
// Restore the state of the current graphics context
CGContextRestoreGState(currentContext);

And this is the result:

I would love any comment or suggestion you can provide about my code or my approach.


#8

My solution for the gold challenge and bronze challenge, posting the draw-rect method.
[size=150]drawRect[/size]

[code]- (void)drawRect:(CGRect)rect
{
CGRect bounds = self.bounds;
//figure out the center of the bounds rectangle
CGPoint center;
center.x = bounds.origin.x + bounds.size.width / 2.0;
center.y = bounds.origin.y + bounds.size.height / 2.0;

//The largest circle will curcumscribe the view
float maxRadius = hypot(bounds.size.width, bounds.size.height)/2;
UIBezierPath *path = [[UIBezierPath alloc] init];

for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) {
    
    [path moveToPoint:CGPointMake(center.x + currentRadius, center.y)];
    [path addArcWithCenter:center radius:currentRadius startAngle:0.0 endAngle:M_PI*2.0 clockwise:YES];
}

//Configure the line width to 10 points
path.lineWidth = 10;

//Configure the drawing color to light gray
[[UIColor lightGrayColor] setStroke];

//Draw the line!
[path stroke];

////GOLD CHALLENGE - GRADIENT
//First we need to have the current context and save it to revert to it after we have done the gradient part
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextSaveGState(currentContext);
//Create a UIBezierPath to use as clipping mask, it must be a triangle
UIBezierPath *triangle = [UIBezierPath bezierPath];
//Then we set the starting point of the path
[triangle moveToPoint:CGPointMake(center.x, center.y + 150)];
//then we add a line from there to center.y-150, center.x -100
[triangle addLineToPoint:CGPointMake(center.x-100, center.y-150)];
//then we add aline from there to the other side of the triangle
[triangle addLineToPoint:CGPointMake(center.x+100, center.y-150)];
//then we just close the path to get the final segment
[triangle closePath];

//then we use that path as the clipping mask
[triangle addClip];

//now we can draw the gradient as described in the book
CGFloat locations[2] = {0.0, 1.0};
CGFloat components[8] = {1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0};
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorspace, components, locations, 2);
CGPoint startPoint = CGPointMake(center.x, center.y + 150.0);
CGPoint endPoint = CGPointMake(center.x, center.y - 150);
CGContextDrawLinearGradient(currentContext, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient);
CGColorSpaceRelease(colorspace);

/////GOLD CHALLENGE - DROP SHADOW//////////
//Restoring to the earliger current context (before gradient and shadow)

CGContextRestoreGState(currentContext);
CGContextSaveGState(currentContext);
//Set the shadow, using CGSizeMake to set the CGSize offset
CGContextSetShadow(currentContext, CGSizeMake(4, 4), 2);
//Then do the drawing with the bronze challenge

////BRONZE CHALLENGES////////
//Create a UIImage object from image file
//Then draw that image into the view with drawInRect
//The rect must be the size of the image and centered in
//its superview

UIImage *logoImage = [UIImage imageNamed:@"logo"];
[logoImage drawInRect:CGRectMake(center.x-(logoImage.size.width/2), center.y-(logoImage.size.height/2), logoImage.size.width, logoImage.size.height)];

//revert to the old context before new tasks
CGContextRestoreGState(currentContext);

}
[/code]


#9

Here is my solution. Probably not the prettiest, but it works!

[code]//
// BNRHypnosisView.m
// Hypnosister
//
// Created by Arthur Zwitser on 17-03-14.
// Copyright © 2014 Big Nerd Ranch. All rights reserved.
//

#import “BNRHypnosisView.h”

@implementation BNRHypnosisView

  • (id)initWithFrame:(CGRect)frame
    {
    self = [super initWithFrame:frame];
    if (self) {
    // All BNRHypnosisViews start with a clear background color
    self.backgroundColor = [UIColor clearColor];
    }
    return self;
    }

  • (void)drawRect:(CGRect)rect
    {
    CGRect bounds = self.bounds;

    // Figure out the center of the bounds rectangle
    CGPoint center;
    center.x = bounds.origin.x + bounds.size.width / 2.0;
    center.y = bounds.origin.y + bounds.size.height / 2.0;

    // The largest circle with circumscribe the view
    float maxRadius = hypot(bounds.size.width, bounds.size.height) / 2.0;

    UIBezierPath *path = [[UIBezierPath alloc] init];

    for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) {
    [path moveToPoint:CGPointMake(center.x + currentRadius, center.y)];
    [path addArcWithCenter:center
    radius:currentRadius
    startAngle:0.0
    endAngle:M_PI * 2.0
    clockwise:YES];
    }

    path.lineWidth = 10;
    [[UIColor lightGrayColor] setStroke];
    [path stroke];

    // Set the currentContext variable
    CGContextRef currentContext = UIGraphicsGetCurrentContext();

    // Read in the image into UIImage
    UIImage *logoImage = [UIImage imageNamed:@“logo.png”];

    CGContextSaveGState(currentContext);

    // Set the path as a triangle and at it to the clip for the gradient
    UIBezierPath *trianglePath = [[UIBezierPath alloc] init];
    [trianglePath moveToPoint:CGPointMake(center.x,
    center.y - logoImage.size.height / 4)];
    [trianglePath addLineToPoint:CGPointMake(center.x - (logoImage.size.width / 4 + 25),
    center.y + (logoImage.size.height / 4 + 25))];
    [trianglePath addLineToPoint:CGPointMake(center.x + (logoImage.size.width / 4 + 25),
    center.y + (logoImage.size.height / 4 + 25))];
    [trianglePath addClip];

    // Draw the gradient
    CGFloat locations[2] = { 0.0, 1.0 };
    CGFloat components[8] = { 1.0, 0.0, 0.0, 1.0,
    1.0, 1.0, 0.0, 1.0 };

    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
    CGGradientRef gradient = CGGradientCreateWithColorComponents(colorspace, components, locations, 2);

    CGPoint startPoint = CGPointMake(center.x,
    center.y - logoImage.size.height / 4);
    CGPoint endPoint = CGPointMake(center.x,
    center.y + (logoImage.size.height / 4 + 25));
    CGContextDrawLinearGradient(currentContext, gradient, startPoint, endPoint, 0);
    CGGradientRelease(gradient);
    CGColorSpaceRelease(colorspace);

    //Restore the GState
    CGContextRestoreGState(currentContext);

    // Set the dropshadow
    CGContextSaveGState(currentContext);
    CGContextSetShadow(currentContext, CGSizeMake(4, 7), 1);

    CGRect imageFrame = CGRectMake(center.x - (logoImage.size.width / 4),
    center.y - (logoImage.size.height / 4),
    logoImage.size.width / 2,
    logoImage.size.height / 2);
    [logoImage drawInRect:imageFrame];

    CGContextRestoreGState(currentContext);
    }

@end
[/code]

Would also like to add that if you want to check for leaks, you can do so by doing command + I and checking the Leaks section of Profiler!


#10

My triangle always seems to be too small even though I’ve set its corners as the same size as the image. Here’s my code.

//  BNRHypnosisView.m
//  Hypnosister
//
//  Created by Lance Aldeosa on 4/4/14.
//  Copyright (c) 2014 Big Nerd Ranch. All rights reserved.
//

#import "BNRHypnosisView.h"

@implementation BNRHypnosisView

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

- (void)drawRect:(CGRect)rect
{
    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 = hypotf(bounds.size.width, bounds.size.height) / 2.0;
    
    UIBezierPath *path = [[UIBezierPath alloc] init];
    
    for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) {
        [path moveToPoint:CGPointMake(center.x + currentRadius, center.y)];
        
        [path addArcWithCenter:center radius:currentRadius startAngle:0.0 endAngle:M_PI * 2.0 clockwise:YES];
    }
    
    path.lineWidth = 10;
    
    [[UIColor lightGrayColor] setStroke];
    
    [path stroke];
    
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    
    //Part 2 of Gold Challenge
    CGContextSaveGState(currentContext);
    
    UIBezierPath *trianglePath = [[UIBezierPath alloc] init];
    
    CGPoint firstPoint = CGPointMake(bounds.size.width / 2.0, (bounds.size.height - 280.5) / 2.0);
    CGPoint secondPoint = CGPointMake((bounds.size.width - 190.0) / 2.0, ((bounds.size.height - 280.5) / 2.0) + 280.5);
    CGPoint thirdPoint = CGPointMake(((bounds.size.width - 190.0) / 2.0) + 190.0, ((bounds.size.height - 280.5) / 2.0) + 280.5);
    
    
    [trianglePath moveToPoint:firstPoint];
    
    [trianglePath addLineToPoint:secondPoint];
    [trianglePath addLineToPoint:thirdPoint];
    [trianglePath closePath];
    
    [trianglePath addClip];
    
    CGFloat locations[2] = {0.0, 1.0};
    CGFloat components[8] = {0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0};
    
    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
    CGGradientRef gradient = CGGradientCreateWithColorComponents(colorspace, components, locations, 2);
    
    CGPoint startPoint = CGPointMake(160.0, 90.0);
    CGPoint endPoint = CGPointMake(160.0, 345.0);
    
    CGContextDrawLinearGradient(currentContext, gradient, startPoint, endPoint, 0);
    
    CGGradientRelease(gradient);
    CGColorSpaceRelease(colorspace);
    
    CGContextRestoreGState(currentContext);
    
    //Part 1 of Gold Challenge
    CGContextSaveGState(currentContext);
    CGContextSetShadow(currentContext, CGSizeMake(4, 7), 3);
    
    //Bronze Challenge
    UIImage *logoImage = [UIImage imageNamed:@"logo.png"];
    
    CGRect imageRect = CGRectMake((bounds.size.width - 190.0) / 2.0 , (bounds.size.height - 280.5) / 2.0, 190.0, 280.5);
    
    [logoImage drawInRect:imageRect];
    
    CGContextRestoreGState(currentContext);
}

@end

Help? Thanks in advance, guys!


#11

My solution (bronze and gold):

[code]// Gold Part 2 - START

CGContextRef currentContext = UIGraphicsGetCurrentContext();

CGContextSaveGState(currentContext);

CGFloat locations[] = { 0.0, 1.0 };
CGFloat components [] = { 0.0, 1.0, 0.0, 1.0,  // Start color is red
                          1.0, 1.0, 0.0, 1.0 }; // End color is yellow

CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorspace, components, locations, 2);

UIBezierPath *trianglePath = [[UIBezierPath alloc] init];

[trianglePath moveToPoint:CGPointMake(center.x, center.y - 150)];
[trianglePath addLineToPoint:CGPointMake(center.x - 125, center.y + 150)];
[trianglePath addLineToPoint:CGPointMake(center.x + 125, center.y + 150)];

CGPoint startPoint;
startPoint.x = center.x;
startPoint.y = center.y - 150;
CGPoint endPoint;
endPoint.x = center.x;
endPoint.y = center.y + 150;

[trianglePath addClip];
CGContextDrawLinearGradient(currentContext, gradient, startPoint, endPoint, 0);

CGGradientRelease(gradient);
CGColorSpaceRelease(colorspace);

CGContextRestoreGState(currentContext);

// Gold Part 2 - END

// Gold Part 1 - START

CGContextSaveGState(currentContext);
CGContextSetShadow(currentContext, CGSizeMake(15,28), 3);

// Bronze - START
UIImage *logoImage = [UIImage imageNamed:@"logo.png"];

CGRect imageRect = CGRectMake(center.x - logoImage.size.width / 4.0, center.y - logoImage.size.height / 4.0, logoImage.size.width / 2.0, logoImage.size.height / 2.0);

[logoImage drawInRect:imageRect];

// Bronze - END

CGContextRestoreGState(currentContext);

// Gold Part 1 - END[/code]

Result: