i’m so full of ideas

iphone: drawing rectangles

April 28, 2009 9:33 am

Mac/AppKit and iPhone/UIKit development are more alike than different. In general, I’d say the iPhone APIs feel more “fresh” than their Mac equivalents. It seems to me like the current crop of Cocoa engineers at Apple have learned a lot in the years since the company ditched “classic” MacOS for an updated version of NextStep, and they’ve used that wisdom to design this new crop of frameworks. And they’re not afraid to revisit past decisions that didn’t work out so well: they discarded MacOSX’s view-origin-in-the-lower-left-corner mistake and put it in the upper left corner for the iPhone, where it belongs.

On the other hand, AppKit is far more complete than UIKit. Almost anything user-interface-related you might want to do on the Mac is covered by APIs in AppKit. UIKit is missing many of those amenities, likely because it is targeted at resource-constrained devices that don’t have the space for dozens of multi-megabyte frameworks. The practical upshot is that I’ve spent a lot of time writing iPhone stuff that AppKit would have provided for me. All the more reason to write blog posts like this one, so coders can google up the answers rather than spending days reinventing wheels.

I like drawing rectangles. Only rarely do they end up in my final apps, but I use them a lot while debugging, to ensure that a view area I’ve staked out is where it should be. On the iPhone, this kind of thing is done with Core Graphics. It is a very complete framework. You can do anything with it. The down side is that CG code tends to be verbose and complicated, requiring lots of function calls to do the simplest things. This is a situation crying out for wrapper functions.

I’ve written a UIView category that provides methods for drawing just about any type of rectangle you can think of: filled or outlined, rounded corners or square, including point-drawing methods. You can provide a color to draw with, or use the current draw color, which is black, unless you change it. You can draw a translucent rectangle by specifying a draw color that has an alpha value of less than one. You can change the radius of the rounded corners drawn by fiddling with the constant kCornerSize at the top of the implementation file.

This is beginner-level stuff, so I guess I should add that UIKit doesn’t let you draw things whenever you feel like it. (That’s true for all GUI environments I’ve ever programmed for, now that I think about it.) You must draw only within a UIView’s drawRect: method. If this is news to you, you should read one of Apple’s introductory texts before continuing.

Here’s a sample drawRect: method you can add to an existing view to demonstrate the rect-drawing methods:

-(void)drawRect:(CGRect)rect {     CGRect  test = CGRectMake(0.0, 0.0, 20.0, 20.0);     [self strokeRect:test color:[UIColor magentaColor]];     test.origin.x += test.size.width;     [self strokeRoundRect:test color:[UIColor yellowColor]];     test.origin.x += test.size.width;     [self fillRect:test color:[UIColor blueColor]];     test.origin.x += test.size.width;     [self fillRoundRect:test color:[UIColor redColor]]; }

… which produces the output shown in the screen-shot. here’s the header file for the category:

// WBViewRect.h -- rectangle drawing methods for UIView // by allen brunson  march 2 2009 #ifndef WBVIEWRECT_H #define WBVIEWRECT_H #import <UIKit/UIKit.h> @interface UIView (WBViewRect) // save and restore graphics context -(void)contextRestore:(CGContextRef)context; -(CGContextRef)contextSave; // points -(void)drawPoint:(CGPoint)point; -(void)drawPoint:(CGPoint)point color:(UIColor*)color; // filled rects -(void)fillRect:(CGRect)rect; -(void)fillRect:(CGRect)rect color:(UIColor*)color; // filled rects with rounded corners -(void)fillRoundRect:(CGRect)rect; -(void)fillRoundRect:(CGRect)rect color:(UIColor*)color; // outlined rects -(void)strokeRect:(CGRect)rect; -(void)strokeRect:(CGRect)rect color:(UIColor*)color; // outlined rects with rounded corners -(void)strokeRoundRect:(CGRect)rect; -(void)strokeRoundRect:(CGRect)rect color:(UIColor*)color; @end #endif  // WBVIEWRECT_H

Finally, here’s the implementation file:

// WBViewRect.mm -- rectangle drawing methods for UIView // by allen brunson  march 2 2009 #include "WBViewRect.h" #pragma mark module data static const CGFloat kCornerSize = 5.0; static CGRect rectStrokeAdjust(CGRect rect) {     rect = CGRectIntegral(rect);     rect.origin.x    += 0.5;     rect.origin.y    += 0.5;     rect.size.width  -= 1.0;     rect.size.height -= 1.0;     return rect; } static void roundRect(CGContextRef context, CGRect rect,  CGFloat ovalWidth, CGFloat ovalHeight) {     CGFloat  fw = 0.0;     CGFloat  fh = 0.0;     assert(ovalWidth  >= 1.0);     assert(ovalHeight >= 1.0);     CGContextSaveGState(context);     CGContextTranslateCTM(context, CGRectGetMinX(rect), CGRectGetMinY(rect));     CGContextScaleCTM(context, ovalWidth, ovalHeight);     fw = rect.size.width  / ovalWidth;     fh = rect.size.height / ovalHeight;     CGContextMoveToPoint(context, fw, fh / 2.0);     CGContextAddArcToPoint(context, fw, fh, fw / 2.0, fh, 1.0);     CGContextAddArcToPoint(context, 0.0, fh, 0, fh / 2.0, 1.0);     CGContextAddArcToPoint(context, 0.0, 0.0, fw / 2.0, 0, 1.0);     CGContextAddArcToPoint(context, fw, 0.0, fw, fh / 2.0, 1.0);     CGContextClosePath(context);     CGContextRestoreGState(context); } @implementation UIView (WBRectView) -(void)contextRestore:(CGContextRef)context {     CGContextRestoreGState(context); } -(CGContextRef)contextSave {     CGContextRef  ctxt = UIGraphicsGetCurrentContext();     CGContextSaveGState(ctxt);     return ctxt; } -(void)drawPoint:(CGPoint)point {     [self drawPoint:point color:nil]; } -(void)drawPoint:(CGPoint)point color:(UIColor*)color {     CGRect  rect = CGRectMake(point.x, point.y, 1.0, 1.0);     [self fillRect:rect color:color]; } -(void)fillRect:(CGRect)rect {     [self fillRect:rect color:nil]; } -(void)fillRect:(CGRect)rect color:(UIColor*)color {     CGContextRef  ctxt = [self contextSave];     if (color)     {         CGContextSetFillColorWithColor(ctxt, [color CGColor]);     }         UIRectFill(rect);     [self contextRestore:ctxt]; } -(void)fillRoundRect:(CGRect)rect {     [self fillRoundRect:rect color:nil]; } -(void)fillRoundRect:(CGRect)rect color:(UIColor*)color {     CGContextRef  ctxt = [self contextSave];     roundRect(ctxt, rect, kCornerSize, kCornerSize);     if (color)     {         CGContextSetFillColorWithColor(ctxt, [color CGColor]);     }         CGContextFillPath(ctxt);     [self contextRestore:ctxt]; } -(void)strokeRect:(CGRect)rect {     [self strokeRect:rect color:nil]; } -(void)strokeRect:(CGRect)rect color:(UIColor*)color {     CGContextRef  ctxt = [self contextSave];     if (color)     {         CGContextSetStrokeColorWithColor(ctxt, [color CGColor]);     }         UIRectFrame(rect);     [self contextRestore:ctxt]; } -(void)strokeRoundRect:(CGRect)rect {     [self strokeRoundRect:rect color:nil]; } -(void)strokeRoundRect:(CGRect)rect color:(UIColor*)color {     CGContextRef  ctxt = [self contextSave];     rect = rectStrokeAdjust(rect);     roundRect(ctxt, rect, kCornerSize, kCornerSize);     if (color)     {         CGContextSetStrokeColorWithColor(ctxt, [color CGColor]);     }         CGContextStrokePath(ctxt);     [self contextRestore:ctxt]; } @end

No Responses to “iphone: drawing rectangles”