Silver Challenge Solution: Shapes


#1

I added this code to the very end of the drawRect: method:

[code]
// Silver Challenge: Shapes

// Set line width and color
CGContextSetLineWidth(ctx, 2);
[[UIColor greenColor] setStroke];

// Draw vertical line
CGContextMoveToPoint(ctx, center.x, center.y + 35);
CGContextAddLineToPoint(ctx, center.x, center.y - 35);
CGContextStrokePath(ctx);

// Draw horizontal line
CGContextMoveToPoint(ctx, center.x + 35, center.y);
CGContextAddLineToPoint(ctx, center.x - 35, center.y);
CGContextStrokePath(ctx);
[/code]If you draw the crosshair before you draw the “You are getting sleepy.” code, the crosshair won’t be drawn on top of the text. This is because drawing on a context is like drawing on a canvas.

Then I used the CGContextSaveGState function before the code for drawing “You are getting sleepy”:

// Save graphics state before drawing text with shadow
CGContextSaveGState(ctx);
    
// Create a string
NSString *text = @"You are getting sleepy.";

Then I used the CGContextRestoreGState function after the code for “You are getting sleepy”:

[code]
// Draw the string
[text drawInRect:textRect withFont:font];

// Restore graphics state before drawing text with shadow
CGContextRestoreGState(ctx);
[/code]Doing the save/restore enabled the crosshair to be drawn without a shadow.


#2

Here is what my drawRect: method looks like :

[code]- (void)drawRect:(CGRect)dirtyRect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
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 radius of the circle should be nearly as big as the view

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

// The thickness of the line should be 10 points wide
CGContextSetLineWidth(ctx, 10);

// The color of the line should be gray (red/green/blue = 0.6, alpha = 1.0);

// CGContextSetRGBStrokeColor(ctx, 0.6, 0.6, 0.6, 1.0);
// [[UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1] setStroke];
// [[UIColor lightGrayColor] setStroke];
[[self circleColor]setStroke];

// Draw concentric circles form the outside in
for (float currentRadius = maxRadius; currentRadius > 0; currentRadius-=20) {
    // Bronze challenge
    CGFloat R = rand() % 101 / 100.0;
    CGFloat G = rand() % 101 / 100.0;
    CGFloat B = rand() % 101 / 100.0;
    CGFloat a = (rand()%51 + 50)/100.0;
    [[UIColor colorWithRed:R green:G blue:B alpha:a] setStroke];

            
    // 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);
}


// Set context for crosshair
// The thickness of the line should be 5 points wide
CGContextSetLineWidth(ctx, 2.0);
// Set color green
[[UIColor greenColor]setStroke];
// Save GState
CGContextSaveGState(ctx);




// Create a string
NSString *text = @"You are getting sleepy.";

// Get a font to draw in
UIFont *font = [UIFont boldSystemFontOfSize:28];

CGRect textRect;

// How big is the string when drawn in this font ?
textRect.size = [text sizeWithFont:font];

// Let's put that string in the center of 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 context 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 dark 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];




// Draw the crosshair
// No shadow : drawing must be made after the text

// Restore G state
CGContextRestoreGState(ctx);

// Add a path to the context
CGContextAddArc(ctx, center.x, center.y, 25.0, 0.0, M_PI * 2.0, YES);
CGContextMoveToPoint(ctx, center.x + 10.0, center.y);
CGContextAddLineToPoint(ctx, center.x +40.0 , center.y);
CGContextMoveToPoint(ctx, center.x - 40.0, center.y);
CGContextAddLineToPoint(ctx, center.x - 10.0 , center.y);
CGContextMoveToPoint(ctx, center.x , center.y - 40);
CGContextAddLineToPoint(ctx, center.x , center.y  - 10.0);
CGContextMoveToPoint(ctx, center.x , center.y + 10);
CGContextAddLineToPoint(ctx, center.x , center.y  + 40.0);

// Perform drawing instruction; removes path
CGContextStrokePath(ctx);

}
[/code]

So basically I added this before the “create a string” part :

// Set context for crosshair // The thickness of the line should be 5 points wide CGContextSetLineWidth(ctx, 2.0); // Set color green [[UIColor greenColor]setStroke]; // Save GState CGContextSaveGState(ctx);

And this after the “draw the string” part (at the end of the method)

[code]// Draw the crosshair
// No shadow : drawing must be made after the text

// Restore G state
CGContextRestoreGState(ctx);

// Add a path to the context
CGContextAddArc(ctx, center.x, center.y, 25.0, 0.0, M_PI * 2.0, YES);
CGContextMoveToPoint(ctx, center.x + 10.0, center.y);
CGContextAddLineToPoint(ctx, center.x +40.0 , center.y);
CGContextMoveToPoint(ctx, center.x - 40.0, center.y);
CGContextAddLineToPoint(ctx, center.x - 10.0 , center.y);
CGContextMoveToPoint(ctx, center.x , center.y - 40);
CGContextAddLineToPoint(ctx, center.x , center.y  - 10.0);
CGContextMoveToPoint(ctx, center.x , center.y + 10);
CGContextAddLineToPoint(ctx, center.x , center.y  + 40.0);

// Perform drawing instruction; removes path
CGContextStrokePath(ctx);[/code]

Note that I chose a different style of crosshair …


#3

I really don’t understand the use of CGContextSaveGState and CGContextRestoreGState. Could someone explain??? The documentation on these two is confusing. :blush:

EDIT: Well, I get it now. Saving state before applying the shadow and then restoring to that state (popping on the stack, and then popping it off) avoids the shadow on the crosshair. Nifty little trick.


#4

It’s even easier to disable shadow w/o using of CGContextSaveGState and CGContextRestoreGState.

Very end of -(void)drawRect:(CGRect)dirtyRect

[code] // silver challenge
int crosshairSize = 25;
CGContextSetShadowWithColor(ctx, offset, 0, NULL);
CGContextSetLineWidth(ctx, 3);
[[UIColor greenColor] setStroke];

CGContextMoveToPoint(ctx, center.x - crosshairSize, center.y);
CGContextAddLineToPoint(ctx, center.x + crosshairSize, center.y);

CGContextMoveToPoint(ctx, center.x, center.y - crosshairSize);
CGContextAddLineToPoint(ctx, center.x, center.y + crosshairSize);
CGContextStrokePath(ctx);[/code]

This works fine. Is there any profit of using CGContextSaveGState and CGContextRestoreGState other than learning two new functions?

Thanks.


#5

I like that FreddyF used the circle style crosshair. Nice.

I actually just drew the crosshairs after the text, and I set the shadow to clear so it wouldn’t show. Is that sloppy?

I was wondering about the necessity of saving the context, and I see how it could be useful.


#6

My interpretation, admittedly based on no experience, is that saving and restoring state makes the code much easier to manage.

If you have to manually undo each previous style change, the code gets longer and much more error-prone. This case may have been trivial, but most presumably will not be.


#7

I also did this challenge without using save or restore functions. It makes me worry if maybe I got it wrong:

[code]- (void)drawRect:(CGRect)dirtyRect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
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 radius of the circle should be nearly as big as the view
float maxRadius = hypot(bounds.size.width, bounds.size.height) / 2.0;

//the thickness of the line should be 10 points wide
CGContextSetLineWidth(ctx, 10);

//set line color - multiple ways to do it
//CGContextSetRGBStrokeColor(ctx, 0.6, 0.6, 0.6, 1.0);
//[[UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1] setStroke];
//[[self circleColor] setStroke];

//draw concentric circles from the outside in
for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) {
    //add a path to the contect
    srand(time(NULL));
    CGFloat red = (CGFloat)arc4random()/UINT32_MAX;
    CGFloat blue = (CGFloat)arc4random()/UINT32_MAX;
    CGFloat green = (CGFloat)arc4random()/UINT32_MAX;

    [[UIColor colorWithRed:red green:green blue:blue alpha:1] setStroke];
    CGContextAddArc(ctx, center.x, center.y,
                    currentRadius, 0.0, M_PI*2.0, YES);
    //perform drawing instruction; removes path
    CGContextStrokePath(ctx);
}

[[UIColor greenColor] setStroke];
CGContextSetLineWidth(ctx, 5);
CGContextMoveToPoint(ctx, center.x+50, center.y);
CGContextAddLineToPoint(ctx, center.x-50, center.y);
CGContextMoveToPoint(ctx, center.x, center.y+50);
CGContextAddLineToPoint(ctx, center.x, center.y-50);
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 that string in the center of 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 context 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 dark 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];

}
[/code]


#8

My favorite is Objective007’s solution - it’s simple and what’s the big deal if you reset the state again? I suppose if you were going on later to add more shadowed text it would make sense to save the state and later restore it. Really I think the authors were trying to expose us to a couple more functions.

-Dan


#9

Hello.

Can anybody upload an image on how the application should look like?

I’m kind of lost about the crosshair.

thanks.


#10

OK - I can’t figure out how to add an image here quickly… How about find my tweet over at www.twitter.com/DXZDB
Just add what Objective007 suggested above in this post.

-Dan