Gold Challenge - Your suggestions?


#1

Thank you for checking this topic, hope you will be interested to contribute.

As many people who participate here in discussions I’m learning iOs development without any programming background or knowledge of C or C++.
Let me share with you little tip first. To overcome all little obstacles that occur to me along my journey I developed a pretty good technique. First, when I need to solve a challenge - I have xCode and a .pdf file of the book opened. Adobe reader has feature: “add sticky note”, you can find it in the upper left corner “Comment” -> Annotations -> (yellow icon "Add sticky note). On the page with the challenge I place those annotations where I step by step write what I think need to be done to solve the challenge. For some experienced programmers, some line of code do not need to be explained whereas for us - every little comma and dot doesn’t make any sense.

I was stuck with the gold challenge for at least 4 hours. You might be wondering - what you’ve been doing for 4 hours??? I was trying to find in the API reference how to fill the circle with white color. I thought that logo is a .png file with transparent background therefore, we need our circle to be white. You see, my logic told me - first we create a path (circle). In the chapter, we used the function “set stroke” to set the color of our stroke in given drawing context. From API reference I found a method that suggested that it can fill the closed path with a color (just what I was looking for) - the “CGContextFillPath”. But it didn’t work for me. I kept getting a circle filled with black color. Logically, I tried different methods to set the filling color similar to: CGContextSetRGBFillColor and CGContextSetAlpha. But nothing worked. Sure there is way to simply fill the circle with white. But at that point I gave up and look up golden challenge solution provided with the book.
There I found out that you actually don’t need to fill the circle with white color - although the logo file was a .png it wasn’t with transparent background. So the sequence was: create a circle drawn by a thin black line, paste in the logo file, draw a gradient. I found my mistake. Awesome.

But what are those alien methods: “CGContextSaveGState” , “CGContextRestoreGState”, “CGContextClip” and the whole gradient block?

[code]- (void)drawRect:(CGRect)rect
{
UIImage *img = [UIImage imageNamed:@“Icon@2x.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 maxRadius = hypot(bounds.size.width, bounds.size.height) / 3.0;

CGContextSaveGState(ctx);
CGContextAddArc(ctx, center.x, center.y, maxRadius, 0.0, M_PI * 2.0, YES);
CGContextSetShadowWithColor(ctx, CGSizeMake(0, 1), 2, [[UIColor blackColor] CGColor]);
CGContextStrokePath(ctx);
CGContextRestoreGState(ctx);

CGContextAddArc(ctx, center.x, center.y, maxRadius, 0.0, M_PI * 2.0, YES);
CGContextClip(ctx);

[img drawInRect:bounds];
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
CGFloat components[8] = {0.8, 0.8, 1, 1, 0.8, 0.8, 1, 0};
CGFloat locs[2] = {0, 1};
CGGradientRef gradient = CGGradientCreateWithColorComponents(space, components, locs, 2);
CGContextDrawLinearGradient(ctx, gradient, CGPointMake(bounds.size.width / 2.0, 0), CGPointMake(bounds.size.width / 2.0, bounds.size.height / 2.0), kCGGradientDrawsBeforeStartLocation);

CGColorSpaceRelease(space);
CGGradientRelease(gradient);

}[/code]

Unfortunately, API reference wasn’t written for simple people like me who want to learn to make cool things. After hoping around it from method to method - I still didn’t understand what those methods actually do. Here are questions that I would love to find answers:
Why do we need to save our drawing context?
Why are we saving it even before adding a path to it?
What does the “CGContextRestoreGState” actually do with drawing context?
Why are we creating another arc path if we’ve already drawn our circle?
What does the “CGContextClip” do to our drawing context? Why do we need it?
What every line of gradient creation code mean?

I realize that there are probably sources where I can find answers to those questions, but I couldn’t find anything consistent besides stackOverFlow threads. You can simple point me to the right direction by giving me a link.
Thank you for your time.


#2

API References assume that you are aware of the underlying concepts and fundamentals.
Some API references refer you to Companion guides which deal with the underlying concepts and fundamentals.

For example (in Xcode 4.3.2) the CGContext Reference, for CGContextSaveGState, has the Companion guide Quartz 2D Programming Guide.
Companion guides are a good place to start learning about graphics programming.

Take a look at the following:

  • View Programming Guide for iOS
  • Quartz 2D Programming Guide
  • About Drawing and Printing in iOS
  • Graphics and Drawing in iOS

#3

Hi ZhenyaVlasov

As someone learning iOS myself I found this exercise a real struggle too (I refused to look at the solution until I got it right).
But by the time I got it running it did help cement a few of the graphics drawing principles.

Here’s what I think I learned about the questions you have asked. I’ll let the more experienced members of the forum correct me where I’m wrong!

[quote]Why do we need to save our drawing context?
Why are we saving it even before adding a path to it?
What does the “CGContextRestoreGState” actually do with drawing context? [/quote]
The drawing context is stateful - that means that anything you do to it is remembered and will apply to subsequent actions against that same context.
So, if you set the color or switch on a drop shadow that will apply to anything you do from that point. This is good if you want to draw several things with the same color, thickness, shadow etc.
Saving the state before you start gives you a clean place to go back to later. Once you’ve drawn the ellipse (and set the line color and drop shadow state along the way) you restore the state back to the original ‘clean’ state before you do the next step.

[quote]Why are we creating another arc path if we’ve already drawn our circle?
What does the “CGContextClip” do to our drawing context? Why do we need it? [/quote]
The first arc is used to draw the black circle with the drop shadow that forms the ‘frame’ around the logo.
The arc is added to the context and then CGContextStrokePath is used to actually draw the stroke.

The second arc is used to set up a clipping area - this is a shape that masks the view so that the image is only displayed inside the circle - without this the image would be square and would spill outside the round frame we just drew.
Again the arc is added to the context - it’s the same size and location as the first one but instead of being drawn, CGContextClip is called to set it up as the clipping region. Everything after that point will be clipped to only appear inside that shape.

I’ve got to say the gradient code is nasty!

CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); CGFloat components[8] = {0.8, 0.8, 1, 1, 0.8, 0.8, 1, 0};

The code above defines the start and end color of the gradient. Since the color space is based on RGB (that’s what the first statement is setting up) the values in the component array are the Red, Green and Blue elements of the color plus the alpha channel (which represents how opaque the color is). So, it takes 4 values to describe the gradient’s start color and 4 for it’s end color, hence the 8 values.
In my solution I went from an 80% transparent blue to an 80% transparent white. The code above seems to go from a totally solid light blue to a totally transparent light blue. The effect is similar.

The code above defines the positions in the gradient where each color applies. 0 is the start, 1 is the end, 0.5 is midway through the gradient, etc.
This array can contain any number of positions, which would allow for gradients that go from red to blue to green, or from white to gray and back to white again, etc. I guess the number of elements in the components array must correspond to the number of locations in this one.

CGGradientRef gradient = CGGradientCreateWithColorComponents(space, components, locs, 2); CGContextDrawLinearGradient(ctx, gradient, CGPointMake(bounds.size.width / 2.0, 0), CGPointMake(bounds.size.width / 2.0, bounds.size.height / 2.0), kCGGradientDrawsBeforeStartLocation);

This is where the gradient gets constructed (first line) and then actually drawn to the context (second line).
The two points that are constructed represent the start and end points of the gradient’s center line. Imagine drawing a line between the two points - the gradient will start at one point and end at the other. If the line runs top to bottom, the gradient will start with the first color at the top and the second color at the bottom (a vertical gradient).
If the line described by the points ran left to right the gradient would start with the first color on the left and the second color on the right (i.e. you would have a horizontal gradient).
The line can also run at an angle giving a diagonal gradient.

Hope that all helps at least a little bit!

And to ibex10’s point, this stuff is not intuitive at all so you really have to read up on the underlying concepts.
I’ve had some experience with Windows’ drawing APIs and the older C-based stuff is pretty similar in concept - I think I would have been lost otherwise.

One final comment - writing drawing code like this seems like I’ve travelled back in time 10 years. Hopefully as we continue to learn the platform and the frameworks we’ll find that there are much more productive higher-level APIs we can use instead. It is valuable to understand the underlying mechanisms though and there are always times when you have to drop down to the bare bones when a high level approach cannot quite do what you need, so this is going to be good learning in any case.