Once you get deeper into coding iPhone apps you find that CoreGraphics starts to become a real friend. A friend who lacks in certain areas, because you will still have to piece together some shapes with the shape drawing functions that CG provides.
For sake of reusability you want to put the creation of distinct shapes into their own respective methods. You could make those into C functions like their CG brethren, but for our purposes objC-methods suffice.
This method creates a CGPath for a rounded rect inside the given rectangle with the given radius. We are alternating adding a straight line segment and then a corner by means of AddArcToPoint.
- (CGPathRef) newPathForRoundedRect:(CGRect)rect radius:(CGFloat)radius { CGMutablePathRef retPath = CGPathCreateMutable(); CGRect innerRect = CGRectInset(rect, radius, radius); CGFloat inside_right = innerRect.origin.x + innerRect.size.width; CGFloat outside_right = rect.origin.x + rect.size.width; CGFloat inside_bottom = innerRect.origin.y + innerRect.size.height; CGFloat outside_bottom = rect.origin.y + rect.size.height; CGFloat inside_top = innerRect.origin.y; CGFloat outside_top = rect.origin.y; CGFloat outside_left = rect.origin.x; CGPathMoveToPoint(retPath, NULL, innerRect.origin.x, outside_top); CGPathAddLineToPoint(retPath, NULL, inside_right, outside_top); CGPathAddArcToPoint(retPath, NULL, outside_right, outside_top, outside_right, inside_top, radius); CGPathAddLineToPoint(retPath, NULL, outside_right, inside_bottom); CGPathAddArcToPoint(retPath, NULL, outside_right, outside_bottom, inside_right, outside_bottom, radius); CGPathAddLineToPoint(retPath, NULL, innerRect.origin.x, outside_bottom); CGPathAddArcToPoint(retPath, NULL, outside_left, outside_bottom, outside_left, inside_bottom, radius); CGPathAddLineToPoint(retPath, NULL, outside_left, inside_top); CGPathAddArcToPoint(retPath, NULL, outside_left, outside_top, innerRect.origin.x, outside_top, radius); CGPathCloseSubpath(retPath); return retPath; } |
The method has to be called new-something so that Build&Analyze does not tell you about a memory leak. Having the method name begin with new tells the static analyzer that this method is supposed to return something that the caller has to take care of releasing.
Now, if we want to use this method, then we can do so in any view’s drawRect:
- (void) drawRect:(CGRect)rect { CGContextRef ctx = UIGraphicsGetCurrentContext(); CGRect frame = self.bounds; CGPathRef roundedRectPath = [self newPathForRoundedRect:frame radius:5]; [[UIColor blueColor] set]; CGContextAddPath(ctx, roundedRectPath); CGContextFillPath(ctx); CGPathRelease(roundedRectPath); } |
Having the rounded rectangle shape as a path object allows us to reuse it several times. You could for example draw a gradient inside after having used the shape for clipping, then draw a border and also add a shadow. All from the same shape. At the end we just release it to clean up.
Categories: Recipes
Hi,
great article. But there is a much easier way to achieve the same result. You can use UIBezierPath.
So for the above example you can just do the following:
– (void) drawRect:(CGRect)rect
{
CGRect frame = self.bounds;
UIBezierPath* path = [UIBezierPath bezierPathWithRoundedRect:frame cornerRadius:5.0];
[[UIColor blueColor] setFill];
[path fill];
}
It’s less code, you do not have to care about releasing the path because this is all handled by the UIBezierPath and you can reuse it as it automatically saves the graphics context for you. For more details see the UIBezierPath reference.
Thanks for your easier solution. I think my post was from a time when UIBezierPath was not yet available.