这片文章是通过使用UIKit中的基础API来实现一些iOS上最基本的图形处理,主要是一些线和图形。
UIKit是一层high-level的图形框架,对用户来说,比较方便使用,它能让用户来操作iOS上的基本元素,比如view,window,button等元素,UIKit也是通过一些底层的API交互来实现这些功能的,iOS中主要的图像引擎是Quartz 2D。
iOS中总有一些比较奇怪的术语,比如path,比如context。UIKit中基本的图形操作最终都要落在context上面,这个context就相当于canvas,一个画布。
首先从画线开始。
一条直线,需要两个点,在iOS中,第一个点需要用户通过CGContextMoveToPoint来指定,第二个点通过CGContextAddLineToPoint来指定,这样两个点有了之后,在指定一些其它的属性,比如线宽和颜色,线条就可以画出来了。
在开始代码之前,需要创建一个UIView的子类,一些的操作,都在这个类的-(void)drawRect:(CGRect)rect中执行
/* 获取context */ CGContextRef currentContext = UIGraphicsGetCurrentContext(); /* 设置线条宽度 */ CGContextSetLineWidth(currentContext, 5.0f); /* 指定第一个点 */ CGContextMoveToPoint(currentContext, 50.0f, 10.0f); /* 指定第二个点 */ CGContextAddLineToPoint(currentContext, 100.0f, 200.0f); /* 设置线条颜色 */ [UIColor brownColor] set]; /* 画线 */ CGContextStrokePath(currentContext);
如果继续指定第三,第四个点,iOS则会画出诸如折线这样的图案出来。在代码执行完CGContextAddLineToPoint之后,当前画笔的坐标会停留在最后一个点,即收笔的位置,所以第二条线会从第一条线的终点开始画。
在画折线的时候,有一个参数可以用,这个参数是用来制定折角的类型,有三个值:kCGLineJoinMiter, kCGLineJoinBevel和kCGLineJoinRound,这个参数在CGContextSetLineJoin中指定
- (void)textLineJoin { [self drawRooftopAtTopPointof:CGPointMake(160.0f, 40.0f) textToDisplay:@"Miter" lineJoin:kCGLineJoinMiter]; [self drawRooftopAtTopPointof:CGPointMake(160.0f, 180.0f) textToDisplay:@"Bevel" lineJoin:kCGLineJoinBevel]; [self drawRooftopAtTopPointof:CGPointMake(160.0f, 320.0f) textToDisplay:@"Round" lineJoin:kCGLineJoinRound]; } - (void)drawRooftopAtTopPointof:(CGPoint)paramPoint textToDisplay:(NSString *)paramText lineJoin:(CGLineJoin)paramLineJoin { [[UIColor brownColor] set]; CGContextRef currentContext = UIGraphicsGetCurrentContext(); /* Set the line join */ CGContextSetLineJoin(currentContext, paramLineJoin); CGContextSetLineWidth(currentContext, 20.0f); CGContextMoveToPoint(currentContext, paramPoint.x - 140, paramPoint.y + 100); CGContextAddLineToPoint(currentContext, paramPoint.x, paramPoint.y); /* Extend the line to another point to make the rooftop */ CGContextAddLineToPoint(currentContext, paramPoint.x + 140, paramPoint.y + 100); CGContextStrokePath(currentContext); /* Draw the text in the rooftop using a black color */ [paramText drawAtPoint:CGPointMake(paramPoint.x - 40.0f, paramPoint.y + 60.0f) withAttributes:@{NSFontAttributeName: [UIFont boldSystemFontOfSize:30.0f], NSForegroundColorAttributeName: [UIColor blackColor]}]; }
三段折线与折角的显示效果如下
上面的方法是直接在context上作画,iOS中还提供了另外一只作图方法,叫做path,原理跟context差不了太多,就是一切都定位等操作在path上定义好,然后将path加入到context中,最后完成具体的画图操作。
/* Create the path */ CGMutablePathRef path = CGPathCreateMutable(); CGRect screenBounds = [[UIScreen mainScreen] bounds]; CGPathMoveToPoint(path, NULL, screenBounds.origin.x, screenBounds.origin.y); CGPathAddLineToPoint(path, NULL, screenBounds.size.width, screenBounds.size.height); CGPathMoveToPoint(path, NULL, screenBounds.size.width, screenBounds.origin.y); CGPathAddLineToPoint(path, NULL, screenBounds.origin.x, screenBounds.size.height); /* Get the context that the path has to be drawn on */ CGContextRef currentContext = UIGraphicsGetCurrentContext(); CGContextAddPath(currentContext, path); [[UIColor blueColor] setStroke]; CGContextDrawPath(currentContext, kCGPathStroke); CGPathRelease(path);
有亮点要注意,一是path在操作完后需要释放掉,使用CGPathRelease,另一个是使用CGContextDrawPath时,需要指定画笔颜色的使用方式,上面使用了Stroke。这个参数有三个值,分别时Stroke,Fill和StrokeFill。Stroke是颜色只用来画边框线,Fill是多边形可用来填充内部颜色,StrokeFill就是边框和填充都使用。具体使用什么颜色在UIColor的setStroke和setFill中设置,如果是[[UIColor redColor] set]这样,则表明Stroke和Fill同时设置。
最终会在屏幕上画一个大大的“X”,效果如下:
下来我们看看怎么画一个矩形,(多边形用折线的方式可以完成),矩形有更方便的方法。
- (void)drawingSeveralRectangles { CGMutablePathRef path = CGPathCreateMutable(); CGRect rectangle1 = CGRectMake(10.0f, 10.0f, 200.0f, 300.0f); CGRect rectangle2 = CGRectMake(40.0f, 100.0f, 90.0f, 300.0f); CGRect rectangles[2] = {rectangle1, rectangle2}; CGPathAddRects(path, NULL, (const CGRect *)&rectangles, 2); CGContextRef currentContext = UIGraphicsGetCurrentContext(); CGContextAddPath(currentContext, path); [[UIColor colorWithRed:0.20f green:0.60f blue:0.80f alpha:1.0f] setFill]; [[UIColor blackColor] setStroke]; CGContextSetLineWidth(currentContext, 5.0f); CGContextDrawPath(currentContext, kCGPathFillStroke); CGPathRelease(path); }
给矩形加阴影效果,通过CGContextSetShadowWithColor可以实现,这个函数有四个参数,第一个是需要使用的context;第二个相当于偏移量,也就是在原矩形的偏移x,y值的地方显示阴影效果;第三个参数表示模糊程度,值越小,越清晰,越大,阴影越模糊;第四个就是阴影所使用的颜色值
CGContextRef currentContext = UIGraphicsGetCurrentContext(); CGContextSetShadowWithColor(currentContext, CGSizeMake(10.0f, 10.0f), 20.0f, [[UIColor redColor] CGColor]); CGMutablePathRef path = CGPathCreateMutable(); CGRect firstRect = CGRectMake(55.0f, 60.0f, 150.0f, 150.0f); CGPathAddRect(path, NULL, firstRect); CGContextAddPath(currentContext, path); [[UIColor colorWithRed:0.20f green:0.60f blue:0.80f alpha:1.0f] setFill]; CGContextDrawPath(currentContext, kCGPathFill); CGPathRelease(path);
这里也有一个问题需要注意,就是shadow一旦添加到context中,那么以后所有的在这个context上画出的东西都含有阴影,比如我在画完这个矩形后,再调用一次前面的函数textLineJoin,可以看到效果如下
这也许并不是我们需要的效果,所以需要在设置shadow前先保存context状态,完成我们的操作后,再把context还原回去,使用CGContextSaveGState和CGContextRestoreGState来完成
CGContextRef currentContext = UIGraphicsGetCurrentContext(); /* Save */ CGContextSaveGState(currentContext); CGContextSetShadowWithColor(currentContext, CGSizeMake(10.0f, 10.0f), 20.0f, [[UIColor redColor] CGColor]); CGMutablePathRef path = CGPathCreateMutable(); CGRect firstRect = CGRectMake(55.0f, 60.0f, 150.0f, 150.0f); CGPathAddRect(path, NULL, firstRect); CGContextAddPath(currentContext, path); [[UIColor colorWithRed:0.20f green:0.60f blue:0.80f alpha:1.0f] setFill]; CGContextDrawPath(currentContext, kCGPathFill); CGPathRelease(path); /* Restore */ CGContextRestoreGState(currentContext);
这样画完矩形后,如果我们再一次调用textLineJoin,出来的三条折线就不会再有阴影了