首先我们通过图-1了解iOS图像编程的框架结构:
UIBezierPath位于UIKit,绝大多数图形界面都由UIKit完成。从图中可以看出UIKit依赖于Core Graphics框架,也是基于Core Graphics框架实现的。如果想要完成某些更底层的功能或者追求极致的性能,那么就要用到Core Graphics去完成。UIBezierPath是对Core Graphics框架中的CGPath的封装。两者的本质上都是一样的,都是使用Quartz来绘画。只不过把绘图操作暴露在不同的API层面上,在具体实现上,当然也会有一些细小的差别。
我们用Core Graphics来绘图的一个通常原因就是只是用图片或是图层效果不能轻易地绘制出矢量图形。矢量绘图包含一下这些:
- 任意多边形(不仅仅是一个矩形)
- 斜线或曲线
- 文本
- 渐变
在我们绘制图形之前,我们需要获取到一个图形上下文CGContext,可以把他形象的比喻成画画的画板,试想如果我们没有画板是不是就不能绘画,我们进行绘图也是如此,UIKit提供一个C函数来帮组我们获取图形上下文:UIGraphicsGetCurrentContext()。现在我们对UIBezierPath、Core Graphics以及CGPath之间的关系有了一个大致的了解,那么接下来我通过一个例子来展示它们是如何绘制图形的。
注意:如果不在drawRect:方法中调用UIGraphicsGetCurrentContext()函数,其返回值为nil。当然我们也有其他方法来创建一个图形上下文(譬如使用UIGraphicsBeginImageContext()),我会在代码中为大家展示。
【按照文档中的说法,系统会维护一个CGContextRef的栈,而UIGraphicsGetCurrentContext()会取栈顶的CGContextRef,正确的做法是只在drawRect里调用UIGraphicsGetCurrentContext(),因为在drawRect之前,系统会往栈里面压入一个valid的CGContextRef,除非自己去维护一个CGContextRef,否则不应该在其他地方取CGContextRef。】
code
自定义一个MyView使其继承与UIView,重写drawRect:方法:
//.h文件
#import
@interface ViewController : UIViewController
@end
//.m文件
#import "MyView.h"
@implementation MyView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor grayColor];
}
return self;
}
//使用UIBezierPath绘图
- (void)drawWithBezierPath {
UIBezierPath *bPath = [UIBezierPath bezierPath];
[bPath moveToPoint:CGPointMake(0, 0)];
[bPath addCurveToPoint:CGPointMake(200, 200) controlPoint1:CGPointMake(50, 150) controlPoint2:CGPointMake(150, 50)];
[[UIColor greenColor] setStroke];
[bPath stroke];
}
//使用Graphics绘图
- (void)drawWithGraphics {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(context, 200, 0);
CGContextAddCurveToPoint(context, 150, 150, 50, 50, 0, 200);
[[UIColor redColor] setStroke];
CGContextStrokePath(context);
}
//使用CGPath绘图
- (void)drawWithCGPath {
CGContextRef gc = UIGraphicsGetCurrentContext();
//创建CGMutablePathRef
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddArc(path, NULL, 100, 100, 45, 0, M_PI*2, YES);
//将CGMutablePathRef添加到当前Context内
CGContextAddPath(gc, path);
//设置绘图属性
[[UIColor redColor] setStroke];
CGContextSetLineWidth(gc, 5);
//执行绘画
CGContextStrokePath(gc);
CGPathRelease(path);
}
//CGPath和UIBezierPath混合使用绘图
- (void)drawWithBezierPathAndGraphics {
UIBezierPath* aPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 100) radius:100 startAngle:0 endAngle:M_PI*2 clockwise:YES];
CGPathRef cgPath = aPath.CGPath;
CGMutablePathRef mutablePath = CGPathCreateMutableCopy(cgPath);
CGPathAddRect(mutablePath, NULL, CGRectMake(50, 50, 100, 100));
aPath.CGPath = mutablePath;
aPath.lineWidth = 5;
[[UIColor greenColor] setStroke];
[aPath stroke];
CGPathRelease(mutablePath);
}
- (void)drawRect:(CGRect)rect {
// Drawing code
[self drawWithBezierPath];
[self drawWithGraphics];
[self drawWithCGPath];
[self drawWithBezierPathAndGraphics];
}
以上代码只是简单的演示如何用UIBezierPath和Core Graphics进行绘图,大家可以细心观察彼此之间的差别和联系,也可以大胆的尝试使用其他的绘图API也绘制自己想绘制的图形,譬如复杂的图表、人物肖像、饼状图等等只要是基于矢量图的都能实现。通过以上代码我们可以发现,如果我们需要绘制一张图形,就必须自定义一个view并重写其DrawRect:方法,哪怕只是一个简单的矩形。这样是不是未免有点麻烦,难道就没有其他方法吗?of course,当然有了,我就不卖关子了,直接上代码:
//开始图像绘图
UIGraphicsBeginImageContext(self.view.bounds.size);
//获取当前CGContextRef
CGContextRef gc = UIGraphicsGetCurrentContext();
//创建用于转移坐标的Transform,这样我们不用按照实际显示做坐标计算
CGAffineTransform transform = CGAffineTransformMakeTranslation(50, 50);
//创建CGMutablePathRef
CGMutablePathRef path = CGPathCreateMutable();
//左眼
CGPathAddEllipseInRect(path, &transform, CGRectMake(0, 0, 20, 20));
//右眼
CGPathAddEllipseInRect(path, &transform, CGRectMake(80, 0, 20, 20));
//笑
CGPathMoveToPoint(path, &transform, 100, 50);
CGPathAddArc(path, &transform, 50, 50, 50, 0, M_PI, NO);
//将CGMutablePathRef添加到当前Context内
CGContextAddPath(gc, path);
//设置绘图属性
[[UIColor blueColor] setStroke];
CGContextSetLineWidth(gc, 2);
//执行绘画
CGContextStrokePath(gc);
//从Context中获取图像,并显示在界面上
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *imgView = [[UIImageView alloc] initWithImage:img];
[self.view addSubview:imgView];
最后提醒一下大家,使用CoreFoundation框架创建对象,例如代码中出现的:CGMutablePathRef path = CGPathCreateMutable();不要忘了在绘制完之后使用相应的方法去释放它们:CGPathRelease(path);。否则会造成内存泄漏,ARC是不会帮组我们去管理CoreFoundation创建出来的对象。