Individually Rounded Corners for your iPhone iPleasure

Everyone likes rounded corners (except Internet Explorer, but who cares?). By now, we’re used to simple declarations to get our hot sexy web 2.0 roundedness in Webkit and Firefox:

border-radius: 10px;

Or if you want to get really freaky, you can define radii individually for each corner (unfortunately this syntax varies by rendering engine):

-webkit-border-top-right-radius: 10px;
-webkit-border-bottom-left-radius: 20px;

Now, if there’s anything I’ve learned about developing for the iPhone, it’s that it takes 10 times as much work as web development. Rounded corners were no exception, until iPhone OS 3.0 released this little gem:

#import <QuartzCore/QuartzCore.h>
CALayer* layer = [myUIView layer];
layer.cornerRadius = 10.0;

Suddenly rounding corners on the iPhone was as easy as slicing bread. Yet there is no way to round individual corners like in a web browser, and much more importantly, using cornerRadius will kill your performance – especially on older devices. Sure, you can get by if you just have one or two rounded views and no scrolling, but we should haven’t to limit the glory of our rounded revolution, now should we?

Because I needed excellent performance with many simultaneous views as well as individually rounded corners, I opted to see what else I could come up with. Luckily, there were several examples on the interwebs with good techniques, though none gave me a simple way to round corners individually. So, after a little sweat, I came up with the following code for you, my dear readers, to use and nuture and love:

CGFunctions.h
//
//  CGFunctions.h
//  OccoTouch
//
//  Created by Mike Laurence on 1/11/10.
//  Copyright 2010 Punkbot LLC. All rights reserved.
//

#pragma mark -
#pragma mark Rounded Corners
void CGContextAddRoundedRect(CGContextRef context, CGRect rect, int inset, int ul, int ur, int lr, int ll);
CGFunctions.m
//
//  CGFunctions.m
//  RoundedCorners
//
//  Created by Mike Laurence on 1/11/10.
//  Copyright 2010 Punkbot LLC. All rights reserved.
//

#import "CGFunctions.h"

#pragma mark -
#pragma mark Rounded Corners

void CGContextAddRoundedRect(CGContextRef context, CGRect rect, int inset, int ul, int ur, int lr, int ll) {

    CGContextSaveGState(context);

    int xLeft = rect.origin.x + inset;
    int xRight = rect.origin.x + rect.size.width - inset;
    int yTop = rect.origin.y + inset;
    int yBottom = rect.origin.y + rect.size.height - inset;

    if (ul > 0) {
        CGContextMoveToPoint(context, xLeft, yTop + ul);
        CGContextAddArcToPoint(context, xLeft, yTop, xLeft + ul, yTop, ul);
    }
    else
        CGContextMoveToPoint(context, xLeft, yTop);

    if (ur > 0) {
        CGContextAddLineToPoint(context, xRight - ur, yTop);
        CGContextAddArcToPoint(context, xRight, yTop, xRight, yTop + ur, ur);
    }
    else
        CGContextAddLineToPoint(context, xRight, yTop);

    if (lr > 0) {
        CGContextAddLineToPoint(context, xRight, yBottom - lr);
        CGContextAddArcToPoint(context, xRight, yBottom, xRight - lr, yBottom, lr);
    }
    else
        CGContextAddLineToPoint(context, xRight, yBottom);

    if (ll > 0) {
        CGContextAddLineToPoint(context, xLeft + ll, yBottom);
        CGContextAddArcToPoint(context, xLeft, yBottom, xLeft, yBottom - ll, ll);
    }
    else
        CGContextAddLineToPoint(context, xLeft, yBottom);

    CGContextClosePath(context);
    CGContextRestoreGState(context);
}

(Full sample project)

This draws a path on your graphics context, which can then be used to draw outlines or fill with colors/patterns/etc.

Notes:

  • Because CGContextAddRoundedRect is defined as a function and not a method of some class, you’ll need to do a little configuration to get your compiler to accept it. Mainly, find your project’s precompiled header file (it should be called something like ProjectName_Prefix.pch) and insert the following line:

    #import "CGFunctions.h"

    That will allow you to use the function anywhere in your code.

  • Watch out for the stroke going outside the view rectangle – you may get weird, uneven-looking drawing. I’ve provided an “inset” parameter in the CGContextAddRoundedRect function so that you can avoid just that. A good rule of thumb is to provide the same value to the inset as you do to your stroke width (you may know it as line width, set via the CGContextSetLineWidth function).

Here’s a simple example of using CGContextAddRoundedRect in practice:

- (void) drawRect:(CGRect)rect {
    // Get graphics context for this view
    CGContextRef context = UIGraphicsGetCurrentContext();
   
    // Set our fill color
    [fillColor set];
   
    // Add the path and fill. Note that our inset parameter (3rd param) is equal to our stroke width.
    CGContextAddRoundedRect(context, rect, strokeWidth, upperLeftRadius, upperRightRadius, lowerRightRadius, lowerLeftRadius);
    CGContextFillPath(context);
   
    // Set our stroke color
    [strokeColor set];
   
    // Set our stroke width
    CGContextSetLineWidth(context, strokeWidth);
   
    // Add the path again and fill
    CGContextAddRoundedRect(context, rect, strokeWidth, upperLeftRadius, upperRightRadius, lowerRightRadius, lowerLeftRadius);
    CGContextStrokePath(context);
}

In addition to painting, you can also use paths to mask images and do all kinds of other fun stuff with Quartz (check out this article for some sample image masking code).

That ought to get you started. Now, go forth and spread the rounded corner love!