Core Graphics Tutorial: Curves and Layers

Brian Moakley

This post is also available in: Japanese, Korean

Working with Core Graphics Need Not Be So Rage Producing

Working with Core Graphics Need Not Be So Rage Producing

Welcome back to our Core Graphics tutorial series! In this series, you’ll learn how to get started with Core Graphics – with practical examples.

In tutorials one, two, and three, you learned how to customize a table view from start to finish – just with Core Graphics.

In tutorial four, you learned how to make a custom glossy UIButton with Core Graphics.

In tutorial five, you learned how to create repeating patterns with very few lines of code.

In this tutorial, you will learn the Core Graphics drawing model and how it dictates the order in the order that you draw your shapes.

You’ll also learn how to draw Quadratic and Bezier curves as well as applying transforms to existing shapes.

Finally, you’ll use Core Graphics layers to clone your drawings with the ease and style of a James Bond super-villian. :]

There’s a lot to cover, so make yourself comfortable, crack those knuckles, and fire up Xcode. It’s time to do some drawing.

Getting Started

In this tutorial, you’ll be modifying an app called Rage Tweet. Downloaded the starter project for this tutorial, unpack the files, and launch the project in Xcode. Build and run the app. You should see the following:

Rage Tweet's first launch

Swipe through the different faces and prepare to be amazed. :] Touch a Rage Face to send a tweet. Punch a Rage Face if you think it’s being snarky. Just do note, we here at raywenderlich.com are not responsible for broken devices. :]

Note: This project makes use of the Social framework in order to send tweets. If you’d like to know more about this, check out our tutorial: Beginning Twitter in iOS 6.

In this tutorial, you will be replacing the flat blue background color with a scenic mountain background drawn by Vicki Wenderlich. With each change of emotions, the sky will turn a different color to represent that state.

There is one catch — there is no source photoshop file. This isn’t a case of exporting different background png files for each emotion. You’ll be drawing it all from scratch.

Core Graphics style! :]

Core Graphic’s Painter’s Model

Before writing even one drawing command, it is important that you understand how things are drawn to the screen. Core Graphics utilizes a drawing model called a painter’s model. In a painter’s model, each drawing command will be on top of the previous one.

This is likend to painting on an actual canvas. You may first paint a blue sky on the canvas, and when the paint has finished drying, you may next paint some clouds in the sky. As you paint the clouds, the original blue color behind the clouds is obscured by fresh white paint. You may next paint some shadows on the cloud and now some of the white paint is obscured by a darker paint, giving the cloud some definition.

Here’s an image straight out of Apple’s developer documentation that illustrates this idea:

Core Graphics Painting Model

In summantion, the drawing model determines your drawing order.

Note: Sometimes you may forget about the painter’s model while a drawing a complex shape. This becomes rather obvious when you expect to see a shape but see nothing instead. As a simple debugging tip, I find it is easier to comment out my other drawing commands one at a time until my shape is revealed. If you still do not see the shape, then you will know its not the actual drawing order that is causing the problem.

Worth a thousand words …

It’s time to start drawing. Open up SkyView.m and uncomment the drawRect method. Add the following:

-(void) drawRect: (CGRect) rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
 
    // draw sky
    // draw mountains
    // draw grass
    // draw flowers
 
    CGColorSpaceRelease(colorSpace);
}

If you’ve gone through the previous Core Graphics tutorials, then this code block should be familiar to you now. First you get the current graphics context after which you create a the standard color space for the device. The comments represent the future drawing calls followed by a release of the colorspace.

If you build your app, Xcode will produce a warning regarding the CGContextRef unused. Ignore the warning for now. It will go away once you start using it in the next few sections of the tutorial.

Note the order of the calls. The sky is drawn first since it will be covered by subsequent drawing calls. Had you put the sky last, it cover all the previous drawings.

Also note, in this tutorial, you will not be drawing the complete image such as the tree, the moon, or the clouds. At the end of the tutorial, there are some additional challenges which include these elements.

Drawing the Sky

Here is the review portion of the tutorial. The sky is a gradient. Instead of doing a two color gradient, you’ll be using a three color gradient. Above the drawRect method, add the following code to draw the sky.

-(void) drawSkyInRect: (CGRect) rect inContext: (CGContextRef) context withColorSpace: (CGColorSpaceRef) colorSpace
{
    UIColor * baseColor = [UIColor colorWithRed:148.0/255.0 green:158.0/255.0 blue:183.0/255.0 alpha:1.0];
    UIColor * middleStop = [UIColor colorWithRed:127.0/255.0 green:138.0/255.0 blue:166.0/255.0 alpha:1.0];
    UIColor * farStop = [UIColor colorWithRed:96.0/255.0 green:111.0/255.0 blue:144.0/255.0 alpha:1.0];
 
    CGContextSaveGState(context);
    NSArray * gradientColors = @[(__bridge id)baseColor.CGColor, (__bridge id)middleStop.CGColor, (__bridge id)farStop.CGColor];
    CGFloat locations[] = { 0.0, 0.1, 0.25 };
 
    CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) gradientColors, locations);
 
    CGPoint startPoint = CGPointMake(rect.size.height / 2, 0);
    CGPoint endPoint = CGPointMake(rect.size.height / 2, rect.size.width);
 
    CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
 
    CGGradientRelease(gradient);
    CGContextRestoreGState(context);
}

All of this drawing code is review. If this code is unfamiliar to you, check out the first Core Graphics tutorial which covers gradients.

Next, change the drawRect method to look like this:

-(void) drawRect: (CGRect) rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
 
    [self drawSkyInRect:rect inContext:context withColorSpace:colorSpace];
    // draw mountains
    // draw grass
    // draw flowers
 
    CGColorSpaceRelease(colorSpace);
}

Now build and run the app. You should see the following:

Sky Gradient

You’ll notice that the actual gradient occurs near the top of the rectangle as opposed to being evenly applied throughout the whole of it. This is the actual sky portion of the drawing. The lower half will be obscured by subsequent drawing calls.

With the sky complete, it’s now time to draw the mountains.

Getting Comfortable with Curves

Take a look at the source drawing and observe the mountains. While you can certainly produce the same effect by drawing a series of arcs, a much easier method is to use curves.

There are two kinds of curves available in Core Graphics. One is called a Quadratic Curve and it’s big brother is known as a Bezier Curve. These curves are based on mathematical principles allowing them to infinitely scale, no matter the resolution.

Quadratic Curves and Bezier Curves

If looking at that diagram makes your stomach twist into knots, take a deep breath followed by a shot of whiskey. :] Now look at it again. Feel better? No? All right. Have another shot and realize this… you do not need to know any mathematical concepts to draw these beasts. You can certainly head over the Wikipedia page and dive into all the formulas, but such a trip is optional.

In practice, these curves are actually quite easy to draw. Like any line, you first need to know a start point and an end point. Then, you add a control point. A control point essentially dictates the curve of the line. The closer placed to the line, the less dramatic the curve. By placing the curve farther away from the line, the more pronounced the curve. I like to think of the control points as little magnets pulling the line towards it.

On a practical level, the main difference between a Quadratic Curve and a Bezier are the number of control points. A Quadratic curve has one control point. A Bezier curve has two points. That’s it.

Time to put it to the test.

In SkyView.m, just underneath the drawSkyInRect method, add this new method:

-(void) drawMountainsInRect: (CGRect) rect inContext: (CGContextRef) context withColorSpace: (CGColorSpaceRef) colorSpace
{
    UIColor * darkColor = [UIColor colorWithRed:1.0/255.0 green:93.0/255.0 blue:67.0/255.0 alpha:1];
    UIColor * lightColor = [UIColor colorWithRed:63.0/255.0 green:109.0/255.0 blue:79.0/255.0 alpha:1];
 
    NSArray * mountainColors = @[(__bridge id)darkColor.CGColor, (__bridge id)lightColor.CGColor];
    CGFloat mountainLocations[] = { .1, .2 };
    CGGradientRef mountainGrad = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) mountainColors, mountainLocations);
    CGPoint mountainStart = CGPointMake(rect.size.height / 2, 100);
    CGPoint mountainEnd = CGPointMake(rect.size.height / 2, rect.size.width);
 
    CGContextSaveGState(context);
 
    // More Coming
 
    // Cleanup Code
    CGContextRestoreGState(context);
    CGGradientRelease(mountainGrad);
}

Ah yes, another gradient. As you can see from the source diagram, the mountains start out a deep green and subtly change to a light tanish color. Now, it’s time to draw the actual curves. You’ll start with a Quadratic curve. Make the following additions to the drawMountainsInRect:inContext:withColorSpace: method:

-(void) drawMountainsInRect: (CGRect) rect inContext: (CGContextRef) context withColorSpace: (CGColorSpaceRef) colorSpace
{
    ...
    CGContextSaveGState(context); // Previous Code
 
    CGMutablePathRef backgroundMountains = CGPathCreateMutable();
    CGPathMoveToPoint(backgroundMountains, nil, -5, 157);
    CGPathAddQuadCurveToPoint(backgroundMountains, nil, 30, 129, 77, 157);
 
    // More Coming
 
    // Cleanup Code
    CGPathRelease(backgroundMountains);
    CGContextRestoreGState(context);  // Previous Code
    ...
}

The first thing you do is create a path object. For reference, a path is just an arbitrary shape. While you can draw directly to the context, a path object allows you to hold on to shape to perform any additional manipulations.

The CGPathMoveToPoint call sets the start point of the line. The next bit is where all the magic happens.

CGPathAddQuadCurveToPoint(backgroundMountains, nil, 30, 129, 77, 157);

The first argument is a path object. Core Graphics will be adding this curve to the path.

The second argument is a affine transformation. For example, if you wanted to apply a rotation or you wanted to scale the curve, you would supply the transformation here. You will be using such transformations later in the chapter.

The next two arguments (30 and 129) are the x and y position of the control point.

The final two arguments (77 and 157) are the x and y positions of the end of the line.

In short, you now have a Quadratic curve.

To see this in action, add the following following code underneath the Quadratic Curve to show the curve onscreen:

-(void) drawMountainsInRect: (CGRect) rect inContext: (CGContextRef) context withColorSpace: (CGColorSpaceRef) colorSpace
{
    ...
    CGPathAddQuadCurveToPoint(backgroundMountains, nil, 30, 129, 77, 157);  // Old Code
 
    // Background Mountain Stroking
    CGContextAddPath(context, backgroundMountains);
    CGContextSetStrokeColorWithColor(context,[UIColor blackColor].CGColor);
    CGContextStrokePath(context);
 
    // More Coming
 
    // Cleanup Code
    CGPathRelease(backgroundMountains); // Old Code
}

Now add modify the drawRect: method so it looks like the following:

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
 
    [self drawSkyInRect:rect inContext:context withColorSpace:colorSpace];
    [self drawMountainsInRect:rect inContext:context withColorSpace:colorSpace];
    // draw grass
    // draw flowers
 
    CGColorSpaceRelease(colorSpace);
}

Now build and run the app. You should see the following:

Quardratic Curve

You have created a nice little curve. It’s not the Mona Lisa, but it’s a start.

Now it’s time to tackle a Bezier curve. Underneath the CGPathAddQuadCurveToPoint call, add the following line:

    CGPathAddCurveToPoint(backgroundMountains, nil, 190, 210, 200, 70, 303, 125);

The big difference between this call and the previous call is the addition of another set of x and y points for the next control point.

Like before, the backgroundMountains is a CGPath and the nil value is for any transformations that you may wish to apply.

The next two arguments (190 and 210) are the x and y position of the first control point.

The next two arguments (200 and 70) are the x and y position of the first control point.

The final two arguments (303 and 125) are the x and y positions of the end of the line.

Build and run the app and you should now see the following:

Bezier and Quadratic Curves

The key thing to remember about curves is that the more you use them, the easier it to determine the placement of the control points for your desired curves.

Now it’s time to complete the first set of mountains. Add the falling code to drawMountainsInRect, underneath the call to draw the Bezier Curve:

-(void) drawMountainsInRect: (CGRect) rect inContext: (CGContextRef) context withColorSpace: (CGColorSpaceRef) colorSpace
{
    ...
    CGPathAddCurveToPoint(backgroundMountains, nil, 190, 210, 200, 70, 303, 125); // Old Code
    CGPathAddQuadCurveToPoint(backgroundMountains, nil, 340, 150, 350, 150);
    CGPathAddQuadCurveToPoint(backgroundMountains, nil, 380, 155, 410, 145);
    CGPathAddCurveToPoint(backgroundMountains, nil, 500, 100, 540, 190, 580, 165);
    CGPathAddLineToPoint(backgroundMountains, nil, 580, rect.size.width);
    CGPathAddLineToPoint(backgroundMountains, nil, -5, rect.size.width);
    CGPathCloseSubpath(backgroundMountains);
 
    // Background Mountain Stroking
    CGContextAddPath(context, backgroundMountains); // Old Code
    ...
}

This finishes the mountains with a few more curves that extend beyond the length of an iPhone 5. Add the following code underneath the previous code block.

-(void) drawMountainsInRect: (CGRect) rect inContext: (CGContextRef) context withColorSpace: (CGColorSpaceRef) colorSpace
{
    ...
    CGPathCloseSubpath(backgroundMountains); // Previous Code
 
    // Background Mountain Drawing
    CGContextAddPath(context, backgroundMountains);
    CGContextClip(context);
    CGContextDrawLinearGradient(context, mountainGrad, mountainStart, mountainEnd, 0);
    CGContextSetLineWidth(context, 4);
 
    // Background Mountain Stroking
    ...
}

Build and run the app. It should look like the following:

First pass at the mountains

Now, add some foreground mountains. Replace the // More Coming with the following code. This is underneath the // Background Mountain Stroking code.

-(void) drawMountainsInRect: (CGRect) rect inContext: (CGContextRef) context withColorSpace: (CGColorSpaceRef) colorSpace
{
    ...
    CGContextStrokePath(context);  // Previous Code
 
    // Foreground Mountains
    CGMutablePathRef foregroundMountains = CGPathCreateMutable();
    CGPathMoveToPoint(foregroundMountains, nil, -5, 190);
    CGPathAddCurveToPoint(foregroundMountains, nil, 160, 250, 200, 140, 303, 190);
    CGPathAddCurveToPoint(foregroundMountains, nil, 430, 250, 550, 170, 590, 210);
    CGPathAddLineToPoint(foregroundMountains, nil, 590, 230);
    CGPathAddCurveToPoint(foregroundMountains, nil, 300, 260, 140, 215, 0, 225);
    CGPathCloseSubpath(foregroundMountains);
 
    // Foreground Mountain drawing
    CGContextAddPath(context, foregroundMountains);
    CGContextClip(context);
    CGContextSetFillColorWithColor(context, darkColor.CGColor);
    CGContextFillRect(context, CGRectMake(0, 170, 590, 90));
 
    // Foreground Mountain stroking
    CGContextAddPath(context, foregroundMountains);
    CGContextSetStrokeColorWithColor(context,[UIColor blackColor].CGColor);
    CGContextStrokePath(context);
 
    // Cleanup Code
    CGPathRelease(foregroundMountains);
    CGPathRelease(backgroundMountains); // Previous Code
    ...
}

Now build and run the app. You should see the following:

Mountains Made in Curves

With just a few curves and a splash of gradients, you can already construct a nice looking background.

Drawing the Grass

The grass is a combination of all the things that you just used. Add the following method SkyView.m underneath the drawMountainsInRect method.

-(void) drawGrassInRect: (CGRect) rect inContext: (CGContextRef) context withColorSpace: (CGColorSpaceRef) colorSpace
{
    CGPoint grassStart = CGPointMake(rect.size.height / 2, 100);
    CGPoint grassEnd = CGPointMake(rect.size.height / 2, rect.size.width);
 
    CGContextSaveGState(context);
    CGMutablePathRef grass = CGPathCreateMutable();
    CGPathMoveToPoint(grass, nil, 590, 230);
    CGPathAddCurveToPoint(grass, nil, 300, 260, 140, 215, 0, 225);
    CGPathAddLineToPoint(grass, nil, 0, rect.size.width);
    CGPathAddLineToPoint(grass, nil, 590, rect.size.width);
 
    CGContextAddPath(context, grass);
    CGContextClip(context);
 
    UIColor * lightGreen = [UIColor colorWithRed:39.0/255.0 green:171.0/255.0 blue:95.0/255.0 alpha:1];
    UIColor * darkGreen = [UIColor colorWithRed:0.0/255.0 green:134.0/255.0 blue:61.0/255.0 alpha:1];
 
    NSArray * grassColors = @[ (__bridge id) lightGreen.CGColor, (__bridge id) darkGreen.CGColor];
    CGFloat grassLocations[] = { .3, .4};
    CGGradientRef grassGrad = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) grassColors, grassLocations);
 
    CGContextDrawLinearGradient(context, grassGrad, grassStart, grassEnd, 0);
    CGContextSetLineWidth(context, 1);
    CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
    CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
 
    CGPathRelease(grass);
    CGGradientRelease(grassGrad);
    CGContextRestoreGState(context);
}

This method is just like the last method. It creates a a large path object the clips a bright green gradient to it. To see it in action, change the draw method to look like the following:

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
 
    [self drawSkyInRect:rect inContext:context withColorSpace:colorSpace];
    [self drawMountainsInRect:rect inContext:context withColorSpace:colorSpace];
    [self drawGrassInRect:rect inContext:context withColorSpace:colorSpace];
    // draw flowers
 
    CGColorSpaceRelease(colorSpace);
}

Now build and run and it should look like the following:

Mountain Scene with Grass

Affable Affine Transformas

Drawing flowersThe next step in the process is to add some flowers to the grass. Take a close look at the source image. Instead of looking at the three flowers, just pick one and take a closer look at how it is drawn. You’ll see that each flower is composed of various circles – one for the center and five for the petals. A small curve represents the stem.

Drawing circles is no problem. There is a method called CGPathAddEllipseInRect. With this method, all you need to do is define a CGRect and an ellispe will be drawn in the center of it.

Of course, there is catch. CGRects can only be vertical or horizontal. What if you wanted the ellipse to be drawn at a forty-degree angle?

Introducing affine transforms. Affine transforms modify of coordinate system while still maintaing points, lines, ans shapes. These mathematical functions allow you to rotate, scale, move, and even combine your objects. Since you want to rotate your object, you’ll be wanting to use the CGAffineTransformMakeRotation. Here is how you call it:

CGAffineTransformMakeRotation(radians)

A radian is a standard angular measurement when dealing with circles. Since most people think in terms of degrees as opposed to radians, a simple helper macro can make this function call easier to use.

At the top of SkyView.m, just underneath the import statements, add the following macro:

#define degreesToRadians(x) (M_PI * x / 180.0)

Now, rotating a CGRect is just a matter of supplying an angle. For example, if you wanted to rotate a CGRect by forty-five degrees, you’d write the following code:

CGMutablePathRef flowerPetal = CGPathCreateMutable();
CGAffineTransform transform = CGAffineTransformMakeRotation(degreesToRadians(45))
CGRect rect = CGRectMake(0, 0, 100, 100);
CGPathAddEllipseInRect(flowerPetal, &transfrom, rect);

Pretty easy, eh? Unfortunately, there’s another catch. Rotating paths can be a little frustrating. Typically, you’ll want to rotate a path around particular point. Since a path is just a collection of points, there is no center position just the origin. Thus when you rotate the ellipse, it appear in a different x and y position for where you started.

To make it work, you must reset the origin point, rotate the path, and then restore the previous point. Instead of doing all of this in one method, create a new method for drawing each petal. Just above the drawRect: method, add this new method:

-(void) drawPetalInRect: (CGRect) rect inDegrees: (NSInteger) degrees inContext: (CGContextRef) context
{
    CGContextSaveGState(context);
    CGMutablePathRef flowerPetal = CGPathCreateMutable();
 
    // more coming
}

This is pretty standard by now. You are first saving the graphics state before making any modifications to it and you are also creating a path for the petal. Now, add the following to the method

-(void) drawPetalInRect: (CGRect) rect inDegrees: (NSInteger) degrees inContext: (CGContextRef) context
{
    ...
    float midX = CGRectGetMidX(rect);
    float midY = CGRectGetMidY(rect);
    // more coming
}

Here you are setting up the translation points for the rotation. The points are arbitrary so using the midpoint of the rect will work fine. Now add the following to the bottom and hold on:

-(void) drawPetalInRect: (CGRect) rect inDegrees: (NSInteger) degrees inContext: (CGContextRef) context
{
    ...
    CGAffineTransform transfrom =
        CGAffineTransformConcat(
            CGAffineTransformConcat(CGAffineTransformMakeTranslation(-midX, -midY),CGAffineTransformMakeRotation(degreesToRadians(degrees))),
            CGAffineTransformMakeTranslation(midX, midY));
}

That’s right … transforms within transforms. In case you didn’t know, it’s transforms all the way down :] By nesting transforms, you scale, rotate, and move everything in one step. Here’s the breakdown:

    CGAffineTransformConcat(
        CGAffineTransformMakeTranslation(-midX, -midY),
        CGAffineTransformMakeRotation(degreesToRadians(degrees))),

This call does two things. It creates a movement translation and a rotatation translation, then appends the two together. If you apply this transform to any path, it would move a path up and to the left while also rotating it. Next, you add add that transform with the following:

    CGAffineTransformMakeTranslation(midX, midY)

This call effectively negates the previous movement since it is the positive values. By combining the two of them together, you are effectively rotating the path in place. It’s a lot of work for one simple movement, but hey, we have to justify our paycheck somehow :]

The rest of the method is pretty standard. Here it is in its entirety:

-(void) drawPetalInRect: (CGRect) rect inDegrees: (NSInteger) degrees inContext: (CGContextRef) context
{
    CGContextSaveGState(context);
    CGMutablePathRef flowerPetal = CGPathCreateMutable();
    float midX = CGRectGetMidX(rect);
    float midY = CGRectGetMidY(rect);
    CGAffineTransform transfrom =
                                    CGAffineTransformConcat(
                                                            CGAffineTransformConcat(CGAffineTransformMakeTranslation(-midX, -midY),CGAffineTransformMakeRotation(degreesToRadians(degrees))),
                                                            CGAffineTransformMakeTranslation(midX, midY));
 
    CGPathAddEllipseInRect(flowerPetal, &transfrom, rect);
    CGContextAddPath(context, flowerPetal);
    CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
    CGContextStrokePath(context);
    CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
    CGContextAddPath(context, flowerPetal);
    CGContextFillPath(context);
 
    CGPathRelease(flowerPetal);
    CGContextRestoreGState(context);
}

Creating a flower should be rather easy. Add this method just above the drawRect: method:

-(void) drawFlowersInRect: (CGRect) rect inContext: (CGContextRef) context withColorSpace: (CGColorSpaceRef) colorSpace
{
    CGContextSaveGState(context);
 
    [self drawPetalInRect:CGRectMake(125, 230, 9, 14) inDegrees:0 inContext:context];
    [self drawPetalInRect:CGRectMake(115, 236, 10, 12) inDegrees:300 inContext:context];
    [self drawPetalInRect:CGRectMake(120, 246, 9, 14) inDegrees:5 inContext:context];
    [self drawPetalInRect:CGRectMake(128, 246, 9, 14) inDegrees:350 inContext:context];
    [self drawPetalInRect:CGRectMake(133, 236, 11, 14) inDegrees:80 inContext:context];
 
    CGMutablePathRef center = CGPathCreateMutable();
    CGRect ellipse = CGRectMake(126, 242, 6, 6);
    CGPathAddEllipseInRect(center, NULL, ellipse);
 
    UIColor * orangeColor = [UIColor colorWithRed:255/255.0 green:174/255.0 blue:49.0/255.0 alpha:1.0];
 
    CGContextAddPath(context, center);
    CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
    CGContextStrokePath(context);
    CGContextSetFillColorWithColor(context, orangeColor.CGColor);
    CGContextAddPath(context, center);
    CGContextFillPath(context);
    CGContextMoveToPoint(context, 135, 249);
    CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
    CGContextAddQuadCurveToPoint(context, 145, 250, 133, 270);
    CGContextStrokePath(context);
 
    CGPathRelease(center);
    CGContextRestoreGState(context);
}

Now, adjust the drawRect: to look like the following:

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
 
    [self drawSkyInRect:rect inContext:context withColorSpace:colorSpace];
    [self drawMountainsInRect:rect inContext:context withColorSpace:colorSpace];
    [self drawGrassInRect:rect inContext:context withColorSpace:colorSpace];
    [self drawFlowersInRect:rect inContext:context withColorSpace:colorSpace];
 
    CGColorSpaceRelease(colorSpace);
}

Build and run and you should now see a nice flower just underneath the mountains.

Completed Flower

Attack of the Clones

Drawing the next two flowers should be a relatively easy affair, but Core Graphics provides a way to make it even easier. Instead of figuring out the measurements for two new flowers, you can simply clone the existing one and make a field of them.

Note: To make this even nicer, you could make several permutations of the flower and randomly select flowers when creating your field. This would give the field a diverse and organic feel.

Core Graphics allows you to make copies of you drawings through CGLayer objects. Instead of drawing to the main graphics context, you draw to the layer context. Once you finish drawing to a CGLayer, they act as factories, pumping out copies of each drawing. The drawings are cached making it faster than using regular drawing calls.

A great example of using CGLayers is the United States flag. The flag contains fifty stars against a blue background. While you could have loop through the drawing instructions for one star at a time, the faster method is to draw the star to a CGLayer and make copies of that star.

Give it a shot. Make some alterations to the top drawFlowersInRect:inContext:withColorSpace: method so it looks like the following:

-(void) drawFlowersInRect: (CGRect) rect inContext: (CGContextRef) context withColorSpace: (CGColorSpaceRef) colorSpace
{
    CGContextSaveGState(context);
    CGSize flowerSize = CGSizeMake(300, 300);
    CGLayerRef flowerLayer = CGLayerCreateWithContext(context, flowerSize, NULL);
    CGContextRef flowerContext = CGLayerGetContext(flowerLayer);
    ...
}

After the context, the first thing that you do is create the size of the object that you are drawing. The reason you are using a larger size of the flower is to accomodate the previous drawing calls. In a real production environment, you would match the drawing calls with the size.

Next, you create a new layer by passing in the current graphics context. In the very next line, you extract the layer’s graphic context. From this point onward, you draw to the layer’s context instead of the main graphics context.

Replace the method with the following contents:

-(void) drawFlowersInRect: (CGRect) rect inContext: (CGContextRef) context withColorSpace: (CGColorSpaceRef) colorSpace
{
    CGContextSaveGState(context);
    CGSize flowerSize = CGSizeMake(300, 300);
    CGLayerRef flowerLayer = CGLayerCreateWithContext(context, flowerSize, NULL);
    CGContextRef flowerContext = CGLayerGetContext(flowerLayer);
 
    [self drawPetalInRect:CGRectMake(125, 230, 9, 14) inDegrees:0 inContext:flowerContext];
    [self drawPetalInRect:CGRectMake(115, 236, 10, 12) inDegrees:300 inContext:flowerContext];
    [self drawPetalInRect:CGRectMake(120, 246, 9, 14) inDegrees:5 inContext:flowerContext];
    [self drawPetalInRect:CGRectMake(128, 246, 9, 14) inDegrees:350 inContext:flowerContext];
    [self drawPetalInRect:CGRectMake(133, 236, 11, 14) inDegrees:80 inContext:flowerContext];
 
    CGMutablePathRef center = CGPathCreateMutable();
    CGRect ellipse = CGRectMake(126, 242, 6, 6);
    CGPathAddEllipseInRect(center, NULL, ellipse);
 
    UIColor * orangeColor = [UIColor colorWithRed:255/255.0 green:174/255.0 blue:49.0/255.0 alpha:1.0];
 
    CGContextAddPath(flowerContext, center);
    CGContextSetStrokeColorWithColor(flowerContext, [UIColor blackColor].CGColor);
    CGContextStrokePath(flowerContext);
    CGContextSetFillColorWithColor(flowerContext, orangeColor.CGColor);
    CGContextAddPath(flowerContext, center);
    CGContextFillPath(flowerContext);
    CGContextMoveToPoint(flowerContext, 135, 249);
    CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
    CGContextAddQuadCurveToPoint(flowerContext, 145, 250, 133, 270);
    CGContextStrokePath(flowerContext);
 
    // Clones go here
 
    CGLayerRelease(flowerLayer);
    CGPathRelease(center);
    CGContextRestoreGState(context);
}

As you can see, everything is the same except you are now drawing to the flowerContext instead of the regular context. Once the flower is complete, they only remaining thing to do is to print out copies.

Add the following code above all of the CGRelease functions:

-(void) drawFlowersInRect: (CGRect) rect inContext: (CGContextRef) context withColorSpace: (CGColorSpaceRef) colorSpace
{
    ...
 
    CGContextDrawLayerAtPoint(context, CGPointMake(110, 110), flowerLayer);
    CGContextTranslateCTM (context, 20, 10);
    CGContextDrawLayerAtPoint(context, CGPointMake(0, 0), flowerLayer);
 
    CGLayerRelease(flowerLayer); // Previous Code
    CGPathRelease(center);
    CGContextRestoreGState(context);
}

There are many ways to handle printing out the flowers. Because you are using fixed CGRects, your output point is 0,0. Next you shift the actual context coordinates to accommodate the fixed CGRects. If you did not shift the context, the flowers would all be drawn on top of one another.

Here’s the code for the complete method:

-(void) drawFlowersInRect: (CGRect) rect inContext: (CGContextRef) context withColorSpace: (CGColorSpaceRef) colorSpace
{
    CGContextSaveGState(context);
    CGSize flowerSize = CGSizeMake(300, 300);
    CGLayerRef flowerLayer = CGLayerCreateWithContext(context, flowerSize, NULL);
    CGContextRef flowerContext = CGLayerGetContext(flowerLayer);
 
    [self drawPetalInRect:CGRectMake(125, 230, 9, 14) inDegrees:0 inContext:flowerContext];
    [self drawPetalInRect:CGRectMake(115, 236, 10, 12) inDegrees:300 inContext:flowerContext];
    [self drawPetalInRect:CGRectMake(120, 246, 9, 14) inDegrees:5 inContext:flowerContext];
    [self drawPetalInRect:CGRectMake(128, 246, 9, 14) inDegrees:350 inContext:flowerContext];
    [self drawPetalInRect:CGRectMake(133, 236, 11, 14) inDegrees:80 inContext:flowerContext];
 
    CGMutablePathRef center = CGPathCreateMutable();
    CGRect ellipse = CGRectMake(126, 242, 6, 6);
    CGPathAddEllipseInRect(center, NULL, ellipse);
 
    UIColor * orangeColor = [UIColor colorWithRed:255/255.0 green:174/255.0 blue:49.0/255.0 alpha:1.0];
 
    CGContextAddPath(flowerContext, center);
    CGContextSetStrokeColorWithColor(flowerContext, [UIColor blackColor].CGColor);
    CGContextStrokePath(flowerContext);
    CGContextSetFillColorWithColor(flowerContext, orangeColor.CGColor);
    CGContextAddPath(flowerContext, center);
    CGContextFillPath(flowerContext);
    CGContextMoveToPoint(flowerContext, 135, 249);
    CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
    CGContextAddQuadCurveToPoint(flowerContext, 145, 250, 133, 270);
    CGContextStrokePath(flowerContext);
 
    CGContextDrawLayerAtPoint(context, CGPointMake(0, 0), flowerLayer);
    CGContextTranslateCTM (context, 20, 10);
    CGContextDrawLayerAtPoint(context, CGPointMake(0, 0), flowerLayer);
    CGContextTranslateCTM (context, -30, 5);
    CGContextDrawLayerAtPoint(context, CGPointMake(0, 0), flowerLayer);
    CGContextTranslateCTM (context, -20,  -10);
    CGContextDrawLayerAtPoint(context, CGPointMake(0, 0), flowerLayer);
 
    CGLayerRelease(flowerLayer);
    CGPathRelease(center);
    CGContextRestoreGState(context);
}

Build and run and you should see the following:

Completed Flower Field

Finishing the App

Congratulations … you made it this far. It’s now time to add the finishing touches to this future best seller. For each change in emotion, the sky should reflect that state. Thankfully, a “rage” value is being passed into the SkyView class with each swipe on the scroll view. Make the following changes to reflect such inner turmoil.

Replace the drawSkyInRect method with the following:

-(void) drawSkyInRect: (CGRect) rect forMode: (NSInteger) mode inContext: (CGContextRef) context withColorSpace: (CGColorSpaceRef) colorSpace
{
    UIColor * baseColor = nil;
    UIColor * middleStop = nil;
    UIColor * farStop = nil;
 
    switch (mode) {
        case HAPPY:
            baseColor = [UIColor colorWithRed:0/255.0 green:158.0/255.0 blue:183.0/255.0 alpha:1.0];
            middleStop = [UIColor colorWithRed:0.0/255.0 green:255.0/255.0 blue:252.0/255.0 alpha:1.0];
            farStop = [UIColor colorWithRed:255.0/255.0 green:255.0/255.0 blue:255.0/255.0 alpha:1.0];
            break;
 
        case SOMEWHAT_HAPPY:
            baseColor = [UIColor colorWithRed:0/255.0 green:158.0/255.0 blue:183.0/255.0 alpha:1.0];
            middleStop = [UIColor colorWithRed:144.0/255.0 green:152.0/255.0 blue:253.0/255.0 alpha:1.0];
            farStop = [UIColor colorWithRed:96.0/255.0 green:111.0/255.0 blue:144.0/255.0 alpha:1.0];
            break;
 
        case NEUTRAL:
            baseColor = [UIColor colorWithRed:148.0/255.0 green:158.0/255.0 blue:183.0/255.0 alpha:1.0];
            middleStop = [UIColor colorWithRed:127.0/255.0 green:138.0/255.0 blue:166.0/255.0 alpha:1.0];
            farStop = [UIColor colorWithRed:96.0/255.0 green:111.0/255.0 blue:144.0/255.0 alpha:1.0];
            break;
 
        case TICKED_OFF:
            baseColor = [UIColor colorWithRed:255.0/255.0 green:147.0/255.0 blue:167.0/255.0 alpha:1.0];
            middleStop = [UIColor colorWithRed:127.0/255.0 green:138.0/255.0 blue:166.0/255.0 alpha:1.0];
            farStop = [UIColor colorWithRed:107.0/255.0 green:107.0/255.0 blue:107.0/255.0 alpha:1.0];
            break;
 
        case RAGING:
            baseColor = [UIColor colorWithRed:255.0/255.0 green:0/255.0 blue:0/255.0 alpha:1.0];
            middleStop = [UIColor colorWithRed:140.0/255.0 green:33.0/255.0 blue:33.0/255.0 alpha:1.0];
            farStop = [UIColor colorWithRed:0/255.0 green:0/255.0 blue:0/255.0 alpha:1.0];
            break;       
    }
 
    CGContextSaveGState(context);
    NSArray * gradientColors = @[(__bridge id)baseColor.CGColor, (__bridge id)middleStop.CGColor, (__bridge id)farStop.CGColor];
    CGFloat locations[] = { 0.0, 0.1, 0.25 };
 
    CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) gradientColors, locations);
 
    CGPoint startPoint = CGPointMake(rect.size.height / 2, 0);
    CGPoint endPoint = CGPointMake(rect.size.height / 2, rect.size.width);
 
    CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
 
    CGGradientRelease(gradient);
    CGContextRestoreGState(context);
}

Next make the following adjustment to drawInRect: so it looks like this:

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
 
    [self drawSkyInRect:rect forMode:self.rageLevel inContext:context withColorSpace:colorSpace];
    [self drawMountainsInRect:rect inContext:context withColorSpace:colorSpace];
    [self drawGrassInRect:rect inContext:context withColorSpace:colorSpace];
    [self drawFlowersInRect:rect inContext:context withColorSpace:colorSpace];
 
    CGColorSpaceRelease(colorSpace);
}

Next, build and run, then swipe thorugh the different rage faces. Challenge dominated! Hello five star rating! :]

Completed App

Additional Challenges

Beginner Level Challenge – Add the rest of the elements in the original drawing. This includes the clouds, the moon, and the tree. Do this using the techniques that you learned throughout this series.

Intermediate Level Challenge – The gradient color change between swipes is abrupt. Make this change a gradual one so that the gradient dissolves from one mood to another.

Advanced Level Challenge – Repurpose the code to be viewable on the iPad as well. This will entail creating another Storyboard, altering the scroll views to account the larger screen dimension, and changing the drawing code to be independent of screen size.

When you have completed any of these challenges, post a screenshot of your work in the accompanying forum. I’d love to see what you can do.

Where to Go From Here

Thanks for following with this tutorial. You can download the complete code for this project over here.

This about wraps up the Core Graphics tutorial series (at least for the time being!). By this point you should have a firm grasp of the fundamentals of Core Graphics and should be well on your way to use it in your own projects.

If you’re interested in Core Graphics, you might also be interested in learning about animation. If so, a good place to start is the How to Use UIView Animation Tutorial.

Ray and I hope you enjoyed the Core Graphics tutorial series and use some Core Graphics in your apps. If you have any questions or comments, please join the forum discussion below!

Brian Moakley

Brian is not only an iOS developer and fiction writer, he also holds the honor being Razeware‘s first fulltime employee. Brian joins Razeware with experience from companies such as ESPN’s online fantasy team and Disney’s Internet Group. When not writing or coding, Brian enjoys story driven first person shooters, lengthy Minecraft sessions, and any pickup game of Carcassonne.

You can find Brian on Twitter.

User Comments

4 Comments

  • Thank you for the tutorial!

    Here are few typos and one bug:

    By placing the curve farther away from the line, the more pronounced the curve.
    ==>
    By placing the control point farther away from the line, the more pronounced the curve.

    The next two arguments (200 and 70) are the x and y position of the first control point.
    ==>
    The next two arguments (200 and 70) are the x and y position of the second control point.

    Add the falling code to drawMountainsInRect, underneath the call to draw the Bezier Curve
    ==>
    Add the following code to drawMountainsInRect, underneath the call to draw the Bezier Curve

    Add the following method SkyView.munderneath the drawMountainsInRect method
    ==>
    Add the following method to SkyView.m underneath the drawMountainsInRect method

    It creates a a large path object the clips a bright green gradient to it.
    ==>
    It creates a large path object then clips a bright green gradient to it.

    If you try to shift beyond the last emotion, it throws SIGABRT on this line in drawSkyInRect, and all the colors are 0:
    NSArray * gradientColors = @[(__bridge id)baseColor.CGColor, (__bridge id)middleStop.CGColor, (__bridge id)farStop.CGColor];
    Shai
  • Hi
    Great tutorial

    Two little details I don't understand in -drawGrass ...

    You don't add
    Code: Select all
        CGContextAddPath(context, grass);

    there, although you did that for the mountains ... could you explain why it's not necessary ?

    Also, you don't have
    Code: Select all
        CGContextStrokePath(context);

    below CGContextSetStrokeColorWithColor.
    Does it mean that the path will never be stroked, but only filled with the gradient ?
    If that's the case, what would be the use of this :
    Code: Select all
        CGContextSetLineWidth(context, 1);
        CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
        CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);


    Thanks for your answers !

    Fred
    FreddyF
  • Thanks for the tutorial.

    I have a question.

    The flower drawn in the CGLayer, not clear than the picture drawn in the graphics context.
    How can I fix this?
    james.park
  • Hi, I drew a quad curve, now I want to move its control point and end points with touch, is there a way to do this? Thanks
    kashif789us

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in May: Procedural Level Generation in Games with Kim Pedersen.

Sign Up - May

Coming up in June: WWDC Keynote - Podcasters React! with the podcasting team.

Sign Up - June

Vote For Our Next Book!

Help us choose the topic for our next book we write! (Choose up to three topics.)

    Loading ... Loading ...

Our Books

Our Team

Tutorial Team

  • Jean-Pierre Distler
  • Felipe Laso Marsetti

... 55 total!

Editorial Team

... 21 total!

Code Team

  • Orta Therox

... 1 total!

Translation Team

  • Sonic Zhao
  • Team Tyran

... 38 total!

Subject Matter Experts

  • Richard Casey

... 4 total!