Core Graphics Tutorial: Arcs and Paths

Ray Wenderlich
Drawing Arcs, Oh My!

Drawing Arcs, Oh My!

Update 4/17/2013 Fully updated for Xcode 4.6, and ARC. (original post by Ray Wenderlich, update by Brian Moakley).

This is the third part of a tutorial series covering how to get started with the Core Graphics API – via practical examples!

In the first part of the series, you learned how to draw lines, rectangles, gradients – via making pretty table view cell backgrounds.

In the second part of the series, you learned how to draw shadows and gloss effects – via making a pretty table view cell header.

In this article, you’ll finally finish up your table view by adding the footer, and adding some finishing touches. You’ll also learn about drawing arcs and working more with clipping and paths along the way!

If you don’t have it already, grab a copy of the sample project where you left off in the last Core Graphics tutorial.

Getting Started

The first thing you’re going to do is replace the footer with a simple 15-point-tall view that just colors the entire view red – just like you did in previous Core Graphics tutorials – to make sure everything is working OK.

At this point, you should be well familiar with how to do this… so why not give it a shot on your own? You can always check back here if you get stuck.




Tomato-San is angry!

Tomato-San is angry!

What?! Are you still reading here?! You can do it – go ahead and try! :]

The Solution

In case you had any troubles, here’s the solution.

Go to File\New\File…, choose the iOS\Cocoa Touch Class\Objective-C class template, and click Next. In the next menu, enter the name CustomFooter as the class name. In the subclass field, type UIView. Click Next then Create.

Switch over to CustomFooter.m, uncomment drawRect, and replace the contents with the following:

- (void)drawRect:(CGRect)rect
    CGContextRef context = UIGraphicsGetCurrentContext();    
    UIColor * redColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0];
    CGContextSetFillColorWithColor(context, redColor.CGColor);
    CGContextFillRect(context, self.bounds);

Then switch to CoolTableViewController.m and make the following modifications:

// In import section
#import "CustomFooter.h"

// Add new methods
-(CGFloat) tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section 
    return 15;

- (UIView *) tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section 
    return [[CustomFooter alloc] init];

Build and run the project, and if all works well you should see the following:

Table View Footer Placeholder

Back to Business

Ok now that you have a placeholder view in place, it’s time to pretty it up. But first, here’s a little refresher of what you’re going for.

Zoomed image of Table View Footer

Note the following about the above:

  • The paper has a neat arc on the bottom.
  • The paper has a gradient from light gray to darker gray.
  • There’s still a white highlight on the edges of the paper.
  • The paper has a shadow that fits to the curve of the arc.

Obviously the new thing here for you is the arc.

Creating Arcs – the Math

An arc is simply a curved line that represents a portion of a circle. In your case the arc you want for the bottom of the paper is the top bit of a very large circle:

Diagram of the circle that our arc is part of

As you can see from this illustration, the arc you want is just the top of a very large circle, with a very large radius, from a certain startAngle to a certain endAngle.

So how do you describe this arc to Core Graphics? Well, the API that you’re going to use is the CGContextAddArc API, and as input to that function you have to tell graphics three things:

  1. The center point of the circle
  2. The radius of the circle
  3. The start point and end point of the line to draw

But darnit, you don’t know any of that, so what in the world are you supposed to do?!

That is where some simple math comes to the rescue. You can actually calculate all of that from what you do know!

The first thing you know is the size of the bounding box for where you want to draw the arc:

Diagram of the rectangle in which our arc will be drawn

The second thing you know is an interesting Math theorem called the Intersecting Chord Theorem , that says if you draw any two lines that link two points in a circle (those are called chords), the products of their segments are equal.

Diagram of the Intersecting Chord Theorem

If you want to understand why that is, visit the above link – it has a cool little javascript demo that you can play with.

Armed with these 2 bits of knowledge, look what happens if you draw two chords like the following:

Diagram of the lines we'll draw to figure out the radius...

So you draw one line connecting the bottom points of your arc rect, and you draw one line from the top of the arc down to the bottom of the cirlce.

If you do that, you know a, b, and c, which lets you figure out d.

So d would be: (a * b) / c. Substituting that out, it’s:

// Just substituting...
CGFloat d = ((arcRectWidth/2) * (arcRectWidth/2)) / (arcRectHeight);
// Or more simply...
CGFloat d = pow(arcRectWidth, 2)/(4*arcRectHeight);

And now that you know c and d, you know that the radius is exactly half that: (c + d) / 2! So substituting:

// Just substituting...
CGFloat radius = (arcRectHeight + (pow(arcRectWidth, 2)/(4*arcRectHeight)))/2;
// Or more simply...
CGFloat radius = (arcRectHeight/2) + (pow(arcRectWidth, 2)/(8*arcRectHeight));

Nice! Now that you know the radius, it’s easy to get the center – you can just subtract the radius from the center point of your shadow rect.

CGPoint arcCenter = CGPointMake(arcRectTopMiddleX, arcRectTopMiddleY - radius);

And once you know the center point, radius, and arcRect, it’s easy to compute the start and end angles with a little trig:

Diagram of how to figure out the start and end angle for an arc from the radius...

You’ll start by figuring out the angle shown in the diagram here. Remember SOH CAH TOA? The cosine of an angle equals the length of the adjacent edge of the triangle divided by the length of the hypotenuse.

So in other words, cosine(angle) = (arcRectWidth/2) / radius. So to get the angle, you just take the arccos:

CGFloat angle = acos(arcRectWidth/(2*radius));

And now that you know that angle, it’s easy to get the start and end angles:

Diagram of how to figure out the start and end angles...

Nice! Now that you understand how it all works, let’s put it all together into a function.

By the way, I think it’s only fair to mention – there’s actually an even easier way to draw an arc like this using the CGContextAddArcToPoint function. You’re going to learn that in the next Core Graphics tutorial in the series, but for now I thought it was useful to cover the math of how to do it this way first.

Drawing Arcs and Creating Paths

Open up Common.h and add the following to the bottom of the file:

static inline double radians (double degrees) { return degrees * M_PI/180; }
CGMutablePathRef createArcPathFromBottomOfRect(CGRect rect, CGFloat arcHeight);

The first thing you add is a helper function to convert degrees to radians.

Then… wait a minute… what’s this CGMutablePathRef thingie?

Remember that there are two steps to drawing in Core Graphics. First you define the path, then you stroke the path or fill the path.

Up until now, you’ve simply added the path you want to stroke/draw directly to the context with functions such as CGContextMoveToPoint, CGContextAddLineToPoint, CGContextAddRect, etc. and then stroked/filled it later with CGContextStrokePath or CGContextFillPath.

Sometimes, you’d also take a shortcut and both add the path to the context and fill it in a single function call, such as CGContextFillRect.

But now, instead of adding the path directly to the context, you’re going to save the path in a special path variable. This will make it easy to reuse the path multiple times, instead of having to call the same functions over and over again.

Making reusable paths is really easy – you just call CGPathXXX variants instead of CGContextXXX variants.

Here’s how it works. Add the following new function to the bottom of Common.m:

CGMutablePathRef createArcPathFromBottomOfRect(CGRect rect, CGFloat arcHeight) 
    CGRect arcRect = CGRectMake(rect.origin.x, rect.origin.y + rect.size.height - arcHeight, rect.size.width, arcHeight);
    CGFloat arcRadius = (arcRect.size.height/2) + (pow(arcRect.size.width, 2) / (8*arcRect.size.height));
    CGPoint arcCenter = CGPointMake(arcRect.origin.x + arcRect.size.width/2, arcRect.origin.y + arcRadius);
    CGFloat angle = acos(arcRect.size.width / (2*arcRadius));
    CGFloat startAngle = radians(180) + angle;
    CGFloat endAngle = radians(360) - angle;
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddArc(path, NULL, arcCenter.x, arcCenter.y, arcRadius, startAngle, endAngle, 0);
    CGPathAddLineToPoint(path, NULL, CGRectGetMaxX(rect), CGRectGetMinY(rect));
    CGPathAddLineToPoint(path, NULL, CGRectGetMinX(rect), CGRectGetMinY(rect));
    CGPathAddLineToPoint(path, NULL, CGRectGetMinX(rect), CGRectGetMaxY(rect));

    return path;    

Ok, so this function takes a rectangle of the entire area, and a float of how big the arc should be (which should be at the bottom of the rectangle). You calculate the arcRect given those two values.

Then you figure out the radius, center, and start and end angles with the math you discussed above.

Next, you create the path. The path will consist of the arc, plus the lines around the edges of the rectangle above the arc.

So first you create the reusable path with CGPathCreateMutable. From then on, you use the CGPathXXX variants instead of the CGContextXXX variants.

You add your arc with CGPathAddArc, passing in all the values you already calculated, then close off the path by drawing lines to each point.

Give it a shot! Replace CustomFooter.m with the following:

#import "CustomFooter.h"

@implementation CustomFooter

#import "Common.h"
- (id)initWithFrame:(CGRect)frame
    self = [super initWithFrame:frame];
    if (self) {
        self.opaque = YES;
        self.backgroundColor = [UIColor clearColor];
    return self;
- (void)drawRect:(CGRect)rect
    CGContextRef context = UIGraphicsGetCurrentContext();
    UIColor * whiteColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0]; 
    UIColor * lightGrayColor = [UIColor colorWithRed:230.0/255.0 green:230.0/255.0 blue:230.0/255.0 alpha:1.0];
    UIColor * darkGrayColor = [UIColor colorWithRed:187.0/255.0 green:187.0/255.0 blue:187.0/255.0 alpha:1.0];
    UIColor * shadowColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:0.5];
    CGFloat paperMargin = 9.0;
    CGRect paperRect = CGRectMake(self.bounds.origin.x+paperMargin, self.bounds.origin.y, self.bounds.size.width-paperMargin*2,  self.bounds.size.height);
    CGRect arcRect = paperRect;
    arcRect.size.height = 8;
    CGMutablePathRef arcPath = createArcPathFromBottomOfRect(arcRect, 4.0);
    CGContextAddPath(context, arcPath);
    drawLinearGradient(context, paperRect, lightGrayColor.CGColor, darkGrayColor.CGColor);


Ok, so you first set the view to opaque and with a clear background color by default.

Note that you can actually set these in initWithFrame and it will get called even though you call the “init” method in CoolTableViewController.

Update: edj from the comments section explains why this is:

“By Cocoa convention there’s one designated initializer (usually the most complete one), which should be called by any other init method of the class. Hence -init calls -initWithFrame for you.”

Next you do the normal Core Graphics setup (context, colors) and create a bounding box for the entire paper area (remember, you need to indent in a bit to match up) and the area you want the arc to be in (the top portion of the rectangle, below that will be some shadows/blank space).

Then, you get the path of the paper (with the arc bottom) by calling the createArcPathFromBottomOfRect method you just wrote. You can then add the path to your context, and clip to that path.

All further drawing will be restricted to that path. So you can use the drawLinearGradient method you wrote earlier and voilà!

One last thing, you release the path when you’re done with it with CFRelease.

Build and run the project, and if all works well you should see the following:

Table Footer, First Draft

Looks decent, but you need to polish it up a bit more.

Clipping, Paths, and the Even-Odd Rule

Next add the following to the bottom of drawRect. Make sure to move CFRelease call to the bottom of the method. Here is the code:

- (void)drawRect:(CGRect)rect
    CGContextRestoreGState(context);  // Previous Code

    CGPoint pointA = CGPointMake(arcRect.origin.x, arcRect.origin.y + arcRect.size.height - 1);
    CGPoint pointB = CGPointMake(arcRect.origin.x, arcRect.origin.y);
    CGPoint pointC = CGPointMake(arcRect.origin.x + arcRect.size.width - 1, arcRect.origin.y);
    CGPoint pointD = CGPointMake(arcRect.origin.x + arcRect.size.width - 1, arcRect.origin.y + arcRect.size.height - 1);
    draw1PxStroke(context, pointA, pointB, whiteColor.CGColor);
    draw1PxStroke(context, pointC, pointD, whiteColor.CGColor);    


Here you draw a thin white line on each edge of the footer to make it pop out a bit more. This should be familiar to you by now based on the past Core Graphics tutorials.

You can build and run to check it out if you’d like. When you’re ready, add the following below what you just added to draw a shadow below the arc. As with the previous code, make sure the CFRelease call is moved to the bottom. Here is the code:

- (void)drawRect:(CGRect)rect
    CGContextRestoreGState(context);  // Previous Code

    CGContextAddRect(context, paperRect);
    CGContextAddPath(context, arcPath);
    CGContextAddPath(context, arcPath);
    CGContextSetShadowWithColor(context, CGSizeMake(0, 2), 3.0, shadowColor.CGColor);


Ok, there’s a new and very important concept going on here, so let’s explain this.

Remember from last Core Graphics tutorial that to draw a shadow, you basically enable shadow drawing, then fill a path. Core Graphics will then fill the path, and also draw the appropriate shadow underneath.

But here, you’ve already filled the path with a gradient – so you don’t want to overwrite that area with a color.

Well, that sounds like a job for clipping! You can set up clipping so that Core Graphics will only draw in the portion OUTSIDE the paper area. Then you can tell it to fill the paper area and draw the shadow, and the paper fill will be ignored (because it’s clipped) but the shadow will show through.

But you don’t have a path for this – the only path you have is for the paper area, not the outside…

Luckily, you can easily get a path for the outside based on the inside, through a neat ability of Core Graphics. What you do is add more than one path to the context, and then call CGContextEOClip.

When you add more than one path to a context, Core Graphics needs some way to determine which points should be filled and which shouldn’t be filled. For example, you could have a donut shape (mmm, donuts!) where the outside is filled but the inside is empty, or a donut hole shape (mmm, donut holes!) where the inside is filled but the outside is not.

You can specify different algorithms to let Core Data know how to handle this. One of the algorithms is “EO”, or even-odd. This means for each point, Core graphics will draw a line from that point to the outside of the drawing area. If the line crosses an odd number of points, it will be filled, otherwise it will not be filled.

Here’s a diagram showing this from the Quartz2D Programming Guide:

Even Odd Rule Diagram

So, by calling the “EO” variant, you’re telling Core Graphics that even though you’ve added two paths to the context, it should treat it as 1 path following the EO rule. So the outside part (the entire paperRect) should be filled, but the inner part (the arcPath) should not be filled.

And then you tell Core Graphics to clip to that path. I.e. only draw in the outside area.

Once you have the clipping area set up, you add the path for the arc, set up the shadow, and fill the arc. Of course, nothing will actually be filled (since it’s clipped), but the shadow will still be drawn in the outside area!

Build and run the project, and if all goes well you should now see a shadow underneath the footer:

Table Footer With Shadows

Finishing Touches

Your table view is looking pretty good, but it needs some last finishing touches.

First, you should fix the worst part: the last table view cell really shouldn’t be drawing a separator like that, it’s jarring. Plus, there’s no difference between selected or not, and the text color changes to an ugly white.

So make the following change to CustomCellBackground.h:

#import <UIKit/UIKit.h>
#import "Common.h"

@interface CustomCellBackground : UIView

@property (nonatomic, assign) BOOL lastCell;
@property (nonatomic, assign) BOOL selected;


Then make the following changes to CustomCellBackground.m:

-(void) drawRect: (CGRect) rect
    CGRect paperRect = self.bounds; // Previous Code

    if (self.selected) {
        drawLinearGradient(context, paperRect, lightGrayColor.CGColor, separatorColor.CGColor);
    } else {
        drawLinearGradient(context, paperRect, whiteColor.CGColor, lightGrayColor.CGColor);


    // Previous Code
    CGPoint endPoint = CGPointMake(paperRect.origin.x + paperRect.size.width - 1, paperRect.origin.y + paperRect.size.height - 1);
    // New Code (replacing last line)
    if (!self.lastCell) {
        draw1PxStroke(context, startPoint, endPoint, separatorColor.CGColor);
    } else {
        CGContextSetStrokeColorWithColor(context, whiteColor.CGColor);
        CGContextSetLineWidth(context, 1.0);
        CGPoint pointA = CGPointMake(paperRect.origin.x, paperRect.origin.y + paperRect.size.height - 1);
        CGPoint pointB = CGPointMake(paperRect.origin.x, paperRect.origin.y);
        CGPoint pointC = CGPointMake(paperRect.origin.x + paperRect.size.width - 1, paperRect.origin.y);
        CGPoint pointD = CGPointMake(paperRect.origin.x + paperRect.size.width - 1, paperRect.origin.y + paperRect.size.height - 1);
        draw1PxStroke(context, pointA, pointB, whiteColor.CGColor);
        draw1PxStroke(context, pointB, pointC, whiteColor.CGColor);
        draw1PxStroke(context, pointC, pointD, whiteColor.CGColor); 

The above should be familiar to you by now. Finally make the following changes to CoolTableViewController.m to look like the following:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    static NSString * CellIdentifier = @"Cell";
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 

    if (![cell.backgroundView isKindOfClass:[CustomCellBackground class]]) {
        CustomCellBackground * backgroundCell = [[CustomCellBackground alloc] init];
        cell.backgroundView = backgroundCell;
    if (![cell.selectedBackgroundView isKindOfClass:[CustomCellBackground class]]) {
        CustomCellBackground * selectedBackgroundCell = [[CustomCellBackground alloc] init];
        selectedBackgroundCell.selected = YES;
        cell.selectedBackgroundView = selectedBackgroundCell;
    NSString * entry;
    if (indexPath.section == 0) {
        entry = self.thingsToLearn[indexPath.row];
        ((CustomCellBackground *) cell.backgroundView).lastCell = indexPath.row == self.thingsToLearn.count - 1;
        ((CustomCellBackground *)cell.selectedBackgroundView).lastCell = indexPath.row == self.thingsToLearn.count - 1;
    } else {
        entry = self.thingsLearned[indexPath.row];
        ((CustomCellBackground *)cell.backgroundView).lastCell = indexPath.row == self.thingsLearned.count - 1;
        ((CustomCellBackground *)cell.selectedBackgroundView).lastCell = indexPath.row == self.thingsLearned.count - 1;

    cell.textLabel.text = entry;
    cell.textLabel.backgroundColor = [UIColor clearColor];
    cell.textLabel.highlightedTextColor = [UIColor blackColor];
    return cell;

Build and run, and you’ll see that things are starting to look better:

Table View Finishing Touches

Next, set up the background and navigation bar to look nicer. Open up MainStoryboard.storyboard, select the Navigation Controller scene, expand the Navigation Controller, and click on the Navigation Bar. In the Attributes Inspector, change the tint color to a nice dark color like the following:

Change Navbar Color

Finally, download this background image made by jaylopez from Drag the image into your project, then open CoolTableViewController.m. At the bottom of viewDidLoad, add the following:

- (void)viewDidLoad
    UIImageView * background = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"main_bg.jpg"]];
    self.tableView.backgroundView = background;

Build and run the project, and if all works well you should see the following:

Finished custom drawn table view

And there you go! A completely custom drawn table view with Core Graphics!

Where To Go From Here?

Here is a sample project with all of the code you’ve developed in this Core Graphics tutorial series.

At this point you should be able to do some pretty cool things with Core Graphics! But there’s more to learn if you’re interested. Next up is a Core Graphics tutorial on creating glossy buttons!

Ray Wenderlich

Ray is part of a great team - the team, a group of over 100 developers and editors from across the world. He and the rest of the team are passionate both about making apps and teaching others the techniques to make them.

When Ray’s not programming, he’s probably playing video games, role playing games, or board games.

Other Items of Interest

Save time.
Learn more with our video courses. Weekly

Sign up to receive the latest tutorials from each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 27 total!

iOS Team

... 83 total!

Android Team

... 43 total!

Unity Team

... 16 total!

Articles Team

... 4 total!

Resident Authors Team

... 31 total!

Podcast Team

... 8 total!

Recruitment Team

... 8 total!

Illustration Team

... 4 total!