文章目录
需求
在许多需要图形显示数据的应用中常常需要进行图形绘制,例如股票金融。在一些特定动画中,我们同样需要结合图形的绘制。
重绘视图
UIView 视图中有一个用来绘制当前图形的方法 -drawRect
方法。我们可以重写该方法,将需要的图形绘制到当前上线文。绘制图形一般有两种方式:CGContextRef
和 UIBezierPath
。
CGContextRef 篇
CGContextRef 属于 Core Graphics 框架,功能强大,我们借助它可以绘制直线、曲线、多边形圆形等各种各样的几何图形,也可以绘制文字、图像等。
Graphics Context 是图形上下文,可以将其理解为一块画布,我们可以在上面进行绘画操作,绘制完成后,将画布放到我们的view中显示即可,view看作是一个画框,CGContextRef 看成 Quartz 2D 绘图环境。
- 使用步骤
1.先在 drawRect 方法中获得上下文 context
2.绘制图形(线,图形,图片等)
3.设置一些修饰属性
4.渲染到上下文,完成绘图
drawRect方法什么时候触发?
view第一次显示到屏幕上时,或者主动调用 setNeedsDisplay
或者 setNeedsDisplayInRect:
方法时。
- 绘图样式
// 矩形
//1.获取图形上下文,即画板
CGContextRef ctx = UIGraphicsGetCurrentContext();
//2.绘制图形
CGContextAddRect(ctx, CGRectMake(10, 10, 40, 40));
//3.设置相关属性
[[UIColor redColor] setStroke]; //颜色 CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextSetLineWidth(ctx, 5); //线条到宽度
//4.渲染到画板上
CGContextStrokePath(ctx); //描边
//CGContextFillPath(ctx); //填充
/**以下为核心部分**/
//圆弧、圆
CGContextAddArc(ctx, 100, 30, 30, 0, M_PI, 0); //最后一个参数表示方向(顺时针或者逆时针)
//椭圆
CGContextAddEllipseInRect(ctx, CGRectMake(150, 10, 80, 40)); //在矩形内绘制椭圆,宽度和高度相等则为圆形
//直线
CGContextMoveToPoint(ctx, 10, 100); //起点
CGContextAddLineToPoint(ctx, kScreenWidth-10, 100); //其他点
CGContextSetRGBStrokeColor(ctx, 0, 1, 0, 1); //RGB类型的颜色
CGContextSetLineCap(ctx, kCGLineCapSquare); //起点和重点
CGContextSetLineJoin(ctx, kCGLineJoinBevel); //转角
CGContextSetLineWidth(ctx, 5); //线条的宽度
//不规则图形
CGContextMoveToPoint(ctx, 20, 150); //设置起点
CGContextAddLineToPoint(ctx, 100, 180); //
CGContextClosePath(ctx);
CGContextSetLineWidth(ctx, 5); //线宽
CGContextSetRGBStrokeColor(ctx, 0, 1, 0, 1); //RGB类型的颜色
CGContextSetLineCap(ctx, kCGLineCapSquare); //终点样式
CGContextSetLineJoin(ctx, kCGLineJoinBevel); //连接点样式
CGContextSetFillColorWithColor(ctx, [UIColor redColor].CGColor); //设置填充色
CGContextSetStrokeColorWithColor(ctx, [UIColor brownColor].CGColor); //路径颜色
/*
kCGPathFill, //填充
kCGPathEOFill,
kCGPathStroke, //线条
kCGPathFillStroke, //线条+填充
kCGPathEOFillStroke
*/
CGContextDrawPath(ctx, kCGPathFillStroke); //fill and stroke
//绘制图片
UIImage *img = [UIImage imageNamed:@"QQ"];
[img drawAtPoint:CGPointMake( 150, 125)]; //绘制到指定的点,图片size
//[img drawInRect:CGRectMake(120, 140, img.size.width, img.size.height)]; //指定位置大小
//[img drawAsPatternInRect:CGRectMake(0, 0, kScreenWidth, kScreenHeight/2.0)]; //多个平铺
//绘制文字
NSString *str = @"少年不识愁滋味,为赋新词强说愁\n而今识得愁滋味,却道天凉好个秋";
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
style.alignment = NSTextAlignmentCenter;
NSDictionary *dic = @{NSForegroundColorAttributeName:[UIColor redColor],
NSFontAttributeName:[UIFont systemFontOfSize:18],
NSParagraphStyleAttributeName:style};
CGSize size = [str sizeWithAttributes:dic];
CGRect strRect = CGRectMake((kScreenWidth-size.width)/2.0, 200, size.width, size.height);
[str drawWithRect:strRect options:NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin attributes:dic context:nil];
UIBezierPath 篇
UIBezierPath 这个类在 UIKit 中, 是 Core Graphics 框架关于 path 的一个面向对象的封装,使用此类可以定义简单的形状,比如我们常用到,矩形,圆形,椭圆,弧,或者不规则的多边形。
UIBezierPath 使用起来非常的方便,创建一个路径对象,然后调用其相关的属性和方法即可完成绘制。
- 使用步骤
1、 重写View的drawRect方法
2、 创建UIBezierPath的对象
3、 使用相关的方法创建路径信息
4、 使用stroke 或者 fill方法结束绘图
- 绘制样式
首先是初始化方式。
// 空路径
UIBezierPath *path = [UIBezierPath bezierPath];
// 矩形路径
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(10, 20, 40, 40)];
// 椭圆路径
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(80, 20, 80, 40)];
// 圆角矩形路径
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(200, 20, 50, 50) cornerRadius:10];
// 圆弧路径,参数:圆弧圆心、圆弧半径、开始弧度、结束弧度、是否为顺时针
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(180, 150) radius:50 startAngle:M_PI endAngle:0 clockwise:YES];
你可以使用 path 进行接下来的图形绘制。
- 绘制直线
// 1、初始化
UIBezierPath *path = [UIBezierPath bezierPath];
// 2、设置路径
[path moveToPoint:CGPointMake(250, 150)]; //起点
[path addLineToPoint:CGPointMake(kScreenWidth-10, 150)]; //移动到下一个点
// 3、设置属性
//[[UIColor redColor] setFill]; //设置填充色
//[UIColor redColor] setStroke]; //设置线条颜色
// 4、渲染绘图
[path stroke]; //描边
// [path fill]; //填充
- 绘制圆弧
圆弧坐标系
// 设置起点
[path moveToPoint:CGPointMake(20, 200)];
// 圆弧圆心、圆弧半径、开始弧度、结束弧度、是否为顺时针
[path addArcWithCenter:CGPointMake(80, 200) radius:40 startAngle:M_PI endAngle:0 clockwise:NO];
- 二次贝塞尔曲线
二次贝塞尔曲线需要一个控制点,两个端点。控制点与两个端点的连线分别是圆弧的两条切线。
// 设置起点
[path moveToPoint:CGPointMake(160, 200)];
// 曲线的终点位置、控制点
[path addQuadCurveToPoint:CGPointMake(200, 200) controlPoint:CGPointMake(170, 150)];
- 三次贝塞尔曲线
// 起点
[path moveToPoint:CGPointMake(220, 230)];
// 曲线的终点位置、第一控制点、第二控制点
[path addCurveToPoint:CGPointMake(300, 180) controlPoint1:CGPointMake(240, 100) controlPoint2:CGPointMake(250, 300)];
- 其他的属性或方法
// 路径的只读属性
empty //是否为空
bounds //封闭所有路径点的最小矩形范围, 不包括控制点
currentPoint //当前的点
// 可以设置的属性
lineWidth //宽度
lineCapStyle //起点终点的样式
/*
kCGLineCapButt, // 默认 (方形结束, 结束位置正好为精确位置),如果线条太宽,看起来不完全封闭
kCGLineCapRound, // (圆形结束, 结束位置超过精确位置半个线宽)
kCGLineCapSquare // (方形结束, 结束位置超过精确位置半个线宽)
*/
lineJoinStyle //线条连接点样式
/*
kCGLineJoinMiter, // 默认
kCGLineJoinRound, // 圆形
kCGLineJoinBevel //
*/
-closePath //封闭路径,可以自动连接起点和终点
-removeAllPoints //移除路径中所有点
-appendPath: //添加一条路径
-bezierPathByReversingPath //路径反转 当前路径的终点作为起点
-applyTransform: //使用CGAffineTransform
-containsPoint: //路径区域中是否包含某个点
外部绘制
通常在自定义视图中重写 -drawrect:
方法即可完成视图的重新绘制,也是我比较推荐的。除了重写方法外,在视图外部也是可以实现图形绘制,比如设置图层 CALayer
代理、使用 CALayer
的子类 CAShapeLayer
。
图层代理
每一个视图都会拥有自己的图层 CALayer
这图层就是用来显示绘制图像的,而视图用来布局,响应处理用户的触摸、点击、滚动等交互事件。 图层有一个协议 CALayerDelegate
,该协议中可以将图层的上下对象 CGContextRef
传递过来,我们只需要在该代理方法中完成绘制即可。
- 使用步骤
设置图层代理
在代理方法中完成绘制
调用图层setNeedsDisplay
- 演示示例
- (void)viewDidLoad {
[super viewDidLoad];
UIView* drawView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
drawView.backgroundColor = UIColor.grayColor;
drawView.center = self.view.center;
drawView.layer.delegate = self; // 设置图层代理
[drawView.layer setNeedsDisplay]; // 一定要调用
[self.view addSubview:drawView];
}
// CALayer的代理,ctx是绘制的上下文
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
CGContextAddRect(ctx, CGRectMake(10, 10, 40, 40));
[[UIColor redColor] setStroke];
CGContextSetLineWidth(ctx, 2);
CGContextStrokePath(ctx);
}
CAShapeLayer
CAShapeLayer
是图层 CALayer
的子类,是和图形相关的图层,单个的 CAShapeLayer 是没有意义的,需要配合路径对象 UIBezierPath 使用。CAShapeLayer 可以看作是 CGContextRef 的面向对象封装,一块图层画板。UIBezierPath 就是这块画板的具体展示。
- 使用步骤
创建 UIBezierPath 对象并添加路径
创建 CAShapeLayer 对象,设置图形属性,如颜色
将 UIBezierPath 对象传递给 CAShapeLayer 对象。
将 CAShapeLayer 添加到视图的图层上显示
- 演示示例
// 路径
UIBezierPath* path = [UIBezierPath bezierPathWithRect:CGRectMake(100, 100, 100, 100)];
CAShapeLayer* shapeLayer = [CAShapeLayer layer];
// 配置相关属性
shapeLayer.strokeColor = UIColor.redColor.CGColor;
shapeLayer.fillColor = UIColor.clearColor.CGColor;
// 路径传递给 shapeLayer
shapeLayer.path = path.CGPath;
// 添加到视图的图层上
[self.view.layer addSublayer:shapeLayer];
更多关于 CAShapeLayer 的使用示例可以参考这篇。
CGContextRef 和 UIBezierPath+CAShapeLayer 对比
CGContextRef 属于 CoreGraphic 框架,使用 CPU 渲染图形,消耗内存较大,CAShapeLayer 属于 CoreAnimation ,通过 GPU 渲染图形,优化性能,动画相关的渲染同样交给了 GPU,不消耗内存。UIBezierPath+CAShapeLayer 方式是面向对象的方式绘图,符合 Ojective-C 面向对象的编程思想。
CoreGraphics 是底层绘制框架,CoreAnimation 中大量使用到CoreGraphics中的类,是一套基于 CoreGraphics 的 Ojective-C 语言封装。
总结
图形可以通过 CGContextRef 进行绘制,绘制方式有两种,一种是自定义视图并重写绘制方法 -drawrect:
,另一种是设置图层代理 delegate
在视图外部绘制。CGContextRef 使用 CPU 来进行图像渲染,消耗大。UIBezierPath+CAShapeLayer 同样可以用来绘制图形,是 Ojective-C 针对 CGContextRef 面向对象的一种封装,这种通过 CAShapeLayer 和 path 的形式灵活多变,并且使用 GPU 直接进行绘制和动画渲染,相比与传统的 CGContextRef,这种方式比较适合 iOS 中的图形绘制。