Episode #33

Core Graphics: Polygons

14 minutes
Published on September 13, 2012

This video is only available to subscribers. Get access to this video and 572 others.

We continue our journey into Core Graphics. This week, we'll draw a polygon with a dynamic number of sides, learn how to use CGMutablePathRef, shadows, clipping paths, and a bit of math.

Episode Links

Supporting Files

We used DrawingHelpers from last time. We'll use them again here:

// DrawingHelpers.h
void drawLinearGradient(CGContextRef context, CGRect rect, CGColorRef startColor, CGColorRef endColor);
void drawGlossyGradient(CGContextRef context, CGRect rect, CGColorRef startColor, CGColorRef endColor);
// DrawingHelpers.m
void drawLinearGradient(CGContextRef context, CGRect rect, CGColorRef startColor, CGColorRef endColor) {
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    CGFloat locations[] = { 0.0, 1.0 };
    NSArray *colors = @[ (__bridge id)(startColor), (__bridge id)endColor ];

    CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);

    CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
    CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));

    CGContextSaveGState(context);

    CGContextAddRect(context, rect);
    CGContextClip(context);

    CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);

    CGContextRestoreGState(context);

    CFRelease(gradient);
    CFRelease(colorSpace);
}

void drawGlossyGradient(CGContextRef context, CGRect rect, CGColorRef startColor, CGColorRef endColor) {
    drawLinearGradient(context, rect, startColor, endColor);

    CGColorRef shineStart = [[UIColor colorWithWhite:0.9 alpha:0.35] CGColor];
    CGColorRef shineEnd = [[UIColor colorWithWhite:0.9 alpha:0.1] CGColor];
    CGRect shineRect = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height / 2.0f);
    drawLinearGradient(context, shineRect, shineStart, shineEnd);
}

Using a GradientView as a background view for our application

This is very simple, just change the class of the view in your XIB to GradientView. The GradientView class looks like this:

// GradientView.m

#import "GradientView.h"
#import "DrawingHelpers.h"

@implementation GradientView

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGColorRef color1 = [[UIColor colorWithWhite:0.35 alpha:1.0f] CGColor];
    CGColorRef color2 = [[UIColor colorWithWhite:0.15 alpha:1.0f] CGColor];
    drawLinearGradient(context, self.bounds, color1, color2);
}

@end

Creating the PolygonView

Finally, we want to show a shape in the middle of the screen. Take a look at the XIB provided in the sample code to see how the slider is wired up to this view.

// PolygonView.h

@interface PolygonView : UIView

@property (nonatomic, assign) NSUInteger numberOfSides;
@property (nonatomic, retain) UIColor *strokeColor;
@property (nonatomic, retain) UIColor *fillColor;

@end
// PolygonView.m

#import "PolygonView.h"
#import "DrawingHelpers.h"

#define MIN_NUM_SIDES 3
#define MAX_NUM_SIDES 100

@implementation PolygonView

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self commonInit];
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self commonInit];
    }
    return self;
}

- (void)commonInit {
    self.backgroundColor = [UIColor clearColor];
    self.numberOfSides = MIN_NUM_SIDES;
    self.fillColor = [UIColor redColor];
    self.strokeColor = [UIColor whiteColor];
}

- (void)setNumberOfSides:(NSUInteger)numberOfSides {
    if (numberOfSides < MIN_NUM_SIDES) {
        numberOfSides = MIN_NUM_SIDES;
    }

    if (numberOfSides > MAX_NUM_SIDES) {
        numberOfSides = MAX_NUM_SIDES;
    }

    _numberOfSides = numberOfSides;
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect {
    if (self.numberOfSides == 0) {
        return;
    }

    CGContextRef context = UIGraphicsGetCurrentContext();

    CGFloat radius = floorf( 0.9 * MIN(self.bounds.size.width, self.bounds.size.height) / 2.0f );

    CGContextSetFillColorWithColor(context, [self.fillColor CGColor]);
    CGContextSetStrokeColorWithColor(context, [self.strokeColor CGColor]);
    CGContextSetLineWidth(context, 6.0);
    CGContextSetLineCap(context, kCGLineCapRound);

    CGMutablePathRef path = CGPathCreateMutable();
    CGFloat startingAngle = 2 * M_PI / self.numberOfSides / 2.0f;
    for (int n = 0; n < self.numberOfSides; n++) {
        CGFloat rotationFactor = ((2 * M_PI) / self.numberOfSides) * (n+1) + startingAngle;
        CGFloat x = (self.bounds.size.width / 2.0f) + cos(rotationFactor) * radius;
        CGFloat y = (self.bounds.size.height / 2.0f) + sin(rotationFactor) * radius;

        if (n == 0) {
            CGPathMoveToPoint(path, NULL, x, y);
        } else {
            CGPathAddLineToPoint(path, NULL, x, y);
        }
    }
    CGPathCloseSubpath(path);

    CGContextSaveGState(context);

    CGContextSetShadow(context, CGSizeMake(0, 10), 2.0);
    CGContextAddPath(context, path);
    CGContextFillPath(context);

    CGContextAddPath(context, path);
    CGContextClip(context);

    drawLinearGradient(context, self.bounds, [self.fillColor CGColor], [[UIColor blueColor] CGColor]);

    CGContextRestoreGState(context);

    CGContextAddPath(context, path);
    CGContextStrokePath(context);



    CFRelease(path);
}
@end