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 Episode Source Code I used XVim in this video to simulate VIM keystrokes inside of Xcode. So far, it works okay. It can be found here: XVim. CGPathRef Class Reference Advanced Drawing Techniques (Apple Docs) 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