Core Graphics: Polygons

Episode #33 | 14 minutes | published on September 13, 2012
Subscribers Only
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
blog comments powered by Disqus