My gold challenge solution


#1

I found getting useful information on how to use gradients and clipping quite bothersome so I thought I’d share my solution for others if they are getting stumped and need a hint. I’d also be interested in seeing how others accomplished this task as well.

Code:
gist.github.com/2324399

Screenshot:
cl.ly/2w0Q1K43461J0n1K1t0f


#2

@treehugger How did you set your crosshair to be transparent? Looks good.

I’m having one issue with the shadow part of this challenge. If you look at the screenshot, you can see that parts of the logo shadow are getting cut off around the edges of the square frame:

How can I fix this in drawRect:? Is it an issue with CGContext Save/Restore? Everything else seemed to work.

Also, I’m not clear on the difference between kCGGradientDrawsBeforeStartLocation and kCGGradientDrawsAfterEndLocation. I tested both options in my app and didn’t notice any difference in the gradient drawing.

Here is some of my code:
HypnosisterAppDelegate.m - application:didFinishLaunchingWithOptions:
[code]// Create a CGRect to pass into the initializer.
CGRect logoRect;

// Create and initilize the logoView. Then add it as a subview of HypnosisView.
BNRLogoView *logoView = [[BNRLogoView alloc] initWithFrame];
[view addSubview];
[/code]
BNRLogoView.m - initWithFrame:

[code]- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialize frame. Size the frame to the dimensions of the icon.
UIImage *myImage = [UIImage imageNamed:@“Icon@2x.png”];
CGRect myframe = CGRectMake(frame.origin.x, frame.origin.y, myImage.size.width, myImage.size.height);

    // Set the background color of the frame to clearColor by setting the backgroundColor composition property.
    [self setBackgroundColor:[UIColor clearColor]];
    
    // Assign myFrame to the frame.
    [self setFrame:myframe];
}
return self;

}[/code]
BNRLogoView.m - drawRect:

[code]- (void)drawRect:(CGRect)logoRect
{
// Create a context.
CGContextRef context = UIGraphicsGetCurrentContext();

// Create a CGRect.
CGRect myRect = [self bounds];

// Find the center point of the rect. This will be used later for drawing the gradient.
CGPoint center;
center.x = myRect.origin.x + myRect.size.width / 2.0; 
center.y = myRect.origin.y + myRect.size.height / 2.0;

// Find the top center of the rect. This will be used later for drawing the gradient.
CGPoint topCenter;
topCenter.x = myRect.origin.x + myRect.size.width / 2;
topCenter.y = myRect.origin.y;

CGContextSaveGState(context);

// Set shadow offset 4 points to the right and 3 points down from the circle.
CGSize shadowOffset = CGSizeMake(4, 3);
// Set the shadow color to gray.
CGColorRef shadowColor = [[UIColor grayColor] CGColor];
// Set the shadow parameters and draw it.
CGContextSetShadowWithColor(context, shadowOffset, 2.0, shadowColor);
CGContextFillEllipseInRect(context, myRect);

CGContextRestoreGState(context);

// Add a circle inside the frame.
CGContextAddEllipseInRect(context, myRect);

// Clip the circle.
CGContextClip(context);

// Draw the logo.
UIImage *image = [UIImage imageNamed:@"Icon@2x.png"];
[image drawInRect:myRect];  
    
// Draw a black outline around the circle with a 2 point line width.
[[UIColor blackColor] setStroke];
CGContextSetLineWidth(context, 2);
CGContextStrokeEllipseInRect(context, myRect);

// Create colorspace
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

// Create C array with RGBA values for light blue. Then again for clear.
CGFloat colorComponents[8] = {0, .75, 1, 1, 0, 0, 0, 0};
// Create C array for number of colors and their index values in the array.
CGFloat locations[2] = {0, 1};

// Set the gradient parameters.
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, colorComponents, locations, 2);

// Draw the gradient with a starting point at the top center and ending point in the center.
CGContextDrawLinearGradient(context, gradient, topCenter, center, kCGGradientDrawsBeforeStartLocation);

}
[/code]


#3

@BryanLuby In my solution I created a second rectangle that was the size of the bounds with 5 pixels subtracted from the width and height. I then used that rectangle to draw everything to (and clip the image to). The shadow will be drawn outside the rectangle, but since I took off 5 pixels there is still room in the bounds for it show without getting clipped.


#4

@Stewsburntmonkey That worked perfectly. Thanks for the tip.


#5

@treehugger Thanks for posting this. Having an inner rectangle so the shadow doesn’t get clipped is very clever! The only thing I would add is making the shadow slightly smaller and shifted down so it only appears underneath the logo. I did that with the following code:

    // Draw border
    [[UIColor blackColor] setStroke];
    CGContextSetLineWidth(ctx, borderWidth);
    CGContextAddEllipseInRect(ctx, innerBounds);
    CGContextStrokePath(ctx);
    
    // Draw shadow
    CGRect shadowBounds = CGRectInset(innerBounds, shadowSize, shadowSize);
    CGContextAddEllipseInRect(ctx, shadowBounds);
    CGSize offset = CGSizeMake(0.0, shadowSize);
    CGContextSetShadowWithColor(ctx, offset, shadowSize, [[UIColor blackColor] CGColor]);
    CGContextStrokePath(ctx);

Screenshot:


#6

I saw you drew a nice border with this :

[code] CGContextSetLineWidth(ctx, 2);
[[UIColor blackColor] setStroke];
CGContextAddEllipseInRect(ctx, bounds);

CGContextStrokePath(ctx);[/code]

Where do you put the logo clip section to have a nice result with both the shadow and the border ?
Could you provide us with the entirety of your drawRect: method ?

Thanks
Fred