iOS绘图教程:http://www.cocoachina.com/applenews/devnews/2014/0115/7703.html
本篇博文是为了梳理学习过程中得框架,上边链接是cocoachina的教程,更详细一些
iOS支持两套图形API族:Core Graphics/QuartZ 2D 和OpenGL ES
路径用于描述由一序列线和Bézier曲线构成的2D几何形状
Core Graphics中也有一些用于创建简单路径(比如矩形和椭圆形)的便利函数。对于更为复杂的路径,必须用Core Graphics框架提供的函数自行创建
Bézier曲线是法国数学家“贝塞尔”在工作中发现,任何一条曲线都可以通过与它相切的控制线两端的点的位置来定义。
Bézier曲线
两种方法创建弧度 第一种
void CGContextAddArc (
CGContextRef c,
CGFloat x, //圆心的x坐标
CGFloat y, //圆心的x坐标
CGFloat radius, //圆的半径
CGFloat startAngle, //开始弧度
CGFloat endAngle, //结束弧度
int clockwise //0表示顺时针,1表示逆时针
);
第二种
void CGContextAddArcToPoint (
CGContextRef c,
CGFloat x1, //端点1的x坐标
CGFloat y1, //端点1的y坐标
CGFloat x2, //端点2的x坐标
CGFloat y2, //端点2的y坐标
CGFloat radius //半径
);
Curves
画曲线,一般是一条直线,然后定义几个控制点,使直线变弯曲。
三次曲线函数
void CGContextAddCurveToPoint (
CGContextRef c,
CGFloat cp1x, //控制点1 x坐标
CGFloat cp1y, //控制点1 y坐标
CGFloat cp2x, //控制点2 x坐标
CGFloat cp2y, //控制点2 y坐标
CGFloat x, //直线的终点 x坐标
CGFloat y //直线的终点 y坐标
);
二次曲线函数
void CGContextAddQuadCurveToPoint (
CGContextRef c,
CGFloat cpx, //控制点 x坐标
CGFloat cpy, //控制点 y坐标
CGFloat x, //直线的终点 x坐标
CGFloat y //直线的终点 y坐标
);
Ellipses
void CGContextAddEllipseInRect (
CGContextRef context,
CGRect rect //一矩形
);
如果矩形是一个正方形,那么画出来就是一个圆
执行完函数貌似current point不会变化,没有具体测试过
Rectangles
void CGContextAddRect (
CGContextRef c,
CGRect rect
);
一次性画出多个矩形
void CGContextAddRects (
CGContextRef c,
const CGRect rects[],
size_t count
);
需要注意的是,画矩形有一些特别,current point没有发生变化
Creating a Path
调用函数 CGContextBeginPath 开始创建路径,线调用函数CGContextMoveToPoint设置起点
然后开始画自己想画的路径,注意一下几点:
1.Lines, arcs, and curves,是从current point开始的
2.假如想封闭一条路径,那么调用函数 CGContextClosePath 把当前点和起点连接起来
3.当在画 arcs的时候,Quartz会画一条线从current point 到 starting point
4.画矩形的时候不会有第三条那这样的的一条直线
5.创建完路径后,必须调用 painting 函数 fill or stroke the path,不然不会画上面东东在相应的设备上】
6.开始创建一个新的路径的时候,使用函数 CGContextBeginPath。
重复利用路径的相关函数和数据类型
们有了两大绘图框架的支持以及三种获得图形上下文的方法(drawRect:、drawRect: inContext:、UIGraphicsBeginImageContextWithOptions)。那么我们就有6种绘图的形式。如果你有些困惑了,不用怕,我接下来将说明这6种情况。无需担心还没有具体的绘图命令,你只需关注上下文如何被创建以及我们是在使用UIKit还是Core Graphics。
第一种绘图形式:在UIView的子类方法drawRect:中绘制一个蓝色圆,使用UIKit在Cocoa为我们提供的当前上下文中完成绘图任务。
1. - (void) drawRect: (CGRect) rect {
2.
3. UIBezierPath* p = [UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
4.
5. [[UIColor blueColor] setFill];
6.
7. [p fill];
8.
9. }
第二种绘图形式:使用Core Graphics实现绘制蓝色圆。
1. - (void) drawRect: (CGRect) rect {
2.
3. CGContextRef con = UIGraphicsGetCurrentContext();
4.
5. CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));
6.
7. CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
8.
9. CGContextFillPath(con);
10.
11. }
上边两种方法对比 第一种是使用UIBezierPath 这个方法 自动的在当前view中画出图形
第二种方法 是使用core Graphics 是获得上下文之后进行绘图 CGContextRef
第三种绘图形式:我将在UIView子类的drawLayer:inContext:方法中实现绘图任务。drawLayer:inContext:方法是一个绘制图层内容的代理方法。为了能够调用drawLayer:inContext:方法,我们需要设定图层的代理对象。但要注意,不应该将UIView对象设置为显示层的委托对象,这是因为UIView对象已经是隐式层的代理对象,再将它设置为另一个层的委托对象就会出问题。轻量级的做法是:编写负责绘图形的代理类。在MyView.h文件中声明如下代码:
1. @interface MyLayerDelegate : NSObject
2.
3. @end
然后MyView.m文件中实现接口代码:
1. @implementation MyLayerDelegate
2.
3. - (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx {
4.
5. UIGraphicsPushContext(ctx);
6.
7. UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
8.
9. [[UIColor blueColor] setFill];
10.
11. [p fill];
12.
13. UIGraphicsPopContext();
14.
15. }
16.
17. @end
直接将代理类的实现代码放在MyView.m文件的#import代码的下面,这样感觉好像在使用私有类完成绘图任务(虽然这不是私有类)。需要注意的是,我们所引用的上下文并不是当前上下文,所以为了能够使用UIKit,我们需要将引用的上下文转变成当前上下文。
因为图层的代理是assign内存管理策略,那么这里就不能以局部变量的形式创建MyLayerDelegate实例对象赋值给图层代理。这里选择在MyView.m中增加一个实例变量,因为实例变量默认是strong:
1. @interface MyView () {
2.
3. MyLayerDelegate* _layerDeleagete;
4.
5. }
6.
7. @end
使用该图层代理:
1. MyView *myView = [[MyView alloc] initWithFrame: CGRectMake(0, 0, 320, 480)];
2.
3. CALayer *myLayer = [CALayer layer];
4.
5. _layerDelegate = [[MyLayerDelegate alloc] init];
6.
7. myLayer.delegate = _layerDelegate;
8.
9. [myView.layer addSublayer:myLayer];
10.
11. [myView setNeedsDisplay]; // 调用此方法,drawLayer: inContext:方法才会被调用。
第四种绘图形式: 使用Core Graphics在drawLayer:inContext:方法中实现同样操作,代码如下:
1. - (void)drawLayer:(CALayer*)lay inContext:(CGContextRef)con {
2.
3. CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));
4.
5. CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
6.
7. CGContextFillPath(con);
8.
9. }
最后,演示UIGraphicsBeginImageContextWithOptions的用法,并从上下文中生成一个UIImage对象。生成UIImage对象的代码并不需要等待某些方法被调用后或在UIView的子类中才能去做。
第五种绘图形式: 使用UIKit实现:
1. UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0);
2.
3. UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
4.
5. [[UIColor blueColor] setFill];
6.
7. [p fill];
8.
9. UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
10.
11. UIGraphicsEndImageContext();
解释一下UIGraphicsBeginImageContextWithOptions函数参数的含义:第一个参数表示所要创建的图片的尺寸;第二个参数用来指定所生成图片的背景是否为不透明,如上我们使用YES而不是NO,则我们得到的图片背景将会是黑色,显然这不是我想要的;第三个参数指定生成图片的缩放因子,这个缩放因子与UIImage的scale属性所指的含义是一致的。传入0则表示让图片的缩放因子根据屏幕的分辨率而变化,所以我们得到的图片不管是在单分辨率还是视网膜屏上看起来都会很好。
第六种绘图形式: 使用Core Graphics实现:
1. UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0);
2.
3. CGContextRef con = UIGraphicsGetCurrentContext();
4.
5. CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));
6.
7. CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
8.
9. CGContextFillPath(con);
10.
11. UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
12.
13. UIGraphicsEndImageContext();
UIKit和Core Graphics可以在相同的图形上下文中混合使用。在iOS 4.0之前,使用UIKit和UIGraphicsGetCurrentContext被认为是线程不安全的。而在iOS4.0以后苹果让绘图操作在第二个线程中执行解决了此问题。