Core Graphics绘图基础教程
1、介绍
-
1.1 Core Graphics
- Core Graphics是基于Quartz框架的高保真输出2D图形的渲染引擎。可处理基于路径的绘图、抗锯齿渲染、渐变、图像、颜色管理、PDF文档等。
- Core Graphics提供了一套2D绘图功能的C语言API,使用C结构体和C的函数模拟了一套面向对象的编程机制。
- Core Graphics中没有OC的对象和方法。
- 我们日常开发时所用到的UIKit中的组件都是由Core Graphics进行绘制的。
- 不仅如此,当我们引入UIKit框架时系统会自动引入Core Graphics框架,并且为了方便开发者使用在UIKit内部还对一些常用的绘图API进行了封装。
-
1.2 Core Graphics 常见绘制需求
- 读取生成PDF
- 生成pdf
- 生成ico
- 剪裁
- Core Graphics相比UIBezierPath在使用上更复杂一些,但是支持的效果也更多,程序运行效率更高。所以现在iOS系统上绘图需求基本上都使用Core Graphis来完成。
- https://www.jianshu.com/p/1a1b9c79f758
- https://www.jianshu.com/p/378f070bb357
-
1.3 绘图步骤:
- ①重写drawRect方法
- ②获取上下文(/画笔/绘图环境)
- ③设置绘图属性,lineWidth
- ④渲染
-
1.4 属性修改刷新显示
- 重写属性set方法, 调用 setNeedsDisplay 刷新绘图显示
- (void)setValue:(CGFloat)value { _value = value; // 更新绘图 [self setNeedsDisplay]; }
2、绘制
-
2.0 开启上下文
- (void)drawRect:(CGRect)rect { // 获取上下文(/画笔/绘图环境) CGContextRef context = UIGraphicsGetCurrentContext(); // 设置画笔颜色 CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor); // 线条的宽度 CGContextSetLineWidth(context, 4); // 开始绘制操作 //[self drawLineDash:context]; }
-
2.1 绘制直线
- (void)drawRect:(CGRect)rect { // 获得上下文 CGContextRef context = UIGraphicsGetCurrentContext(); // 获得起点 CGContextMoveToPoint(context, 10, 10); // 画到(100,100) CGContextAddLineToPoint(context, 160, 90); // 线条的宽度 CGContextSetLineWidth(context, 5); // 设置线条颜色 [[UIColor redColor] set]; // 渲染上下文 CGContextStrokePath(context); // 获得起点 CGContextMoveToPoint(context, 15, 40); // 画到(100,100) CGContextAddLineToPoint(context, 150, 150); // 线条的宽度 CGContextSetLineWidth(context, 20); // 设置线条颜色 [[UIColor whiteColor] set]; // 渲染上下文 CGContextStrokePath(context); }
- 效果:
- 注意:
- 打算绘制第二根线前,记得调用渲染上下文 CGContextStrokePath(context) 方法, 否则第一根的线条样式将被第二根线的样式覆盖。
- 打算绘制第二根线前,记得调用渲染上下文 CGContextStrokePath(context) 方法, 否则第一根的线条样式将被第二根线的样式覆盖。
- 效果:
-
2.2 绘制多条线
- 方法一
- (void)drawManyLinesA:(CGContextRef)context { // 例如:三角形 CGContextMoveToPoint(context, 150, 100); CGContextAddLineToPoint(context, 30, 200); CGContextAddLineToPoint(context, 200, 200); // 方法1:回到起点 //CGContextAddLineToPoint(context, 200, 100); // 方法2:闭合(起点和终点连线) //CGContextClosePath(context); // 画线 CGContextStrokePath(context); }
- 方法二
- (void)drawManyLinesB:(CGContextRef)context { CGPoint point[] = {CGPointMake(150, 100), CGPointMake(30, 200), CGPointMake(200, 200)}; // 计算数组中元素个数 // int count = sizeof(数组名)/size(sizeof(a[0])); CGContextAddLines(context, point, sizeof(point) / sizeof(point[0])); // 闭合 CGContextClosePath(context); // 开始画 CGContextStrokePath(context); }
- 效果:
方法一: 方法二:
-
2.3 绘制虚线
- (void)drawLineDash:(CGContextRef)context { // 虚线的样式:长5 空隙15 长10 空隙30 长2 空隙20... CGFloat lengths[] = {5, 15, 10, 30, 2, 20}; /** * phase 开始点 跳过多少个点数(0代表从头画) * lengths 虚线的样式 * count 长度 */ // CGContextSetLineDash(<#CGContextRef c#>, <#CGFloat phase#>, <#const CGFloat *lengths#>, <#size_t count#>) CGContextSetLineDash(context, 1, lengths, sizeof(lengths) / sizeof(lengths[0])); // 起点 CGContextMoveToPoint(context, 10, 10); // 画到 CGContextAddLineToPoint(context, 130, 60); // 画线 CGContextStrokePath(context); // 第二段设置成 直线 NULL CGContextSetLineDash(context, 0, NULL, 0); // 需要重新设置起点 CGContextMoveToPoint(context, 10, 60); // 画到 CGContextAddLineToPoint(context, 120, 150); // 画线 CGContextStrokePath(context); }
- 效果:
- 效果:
-
2.4 绘制矩形
- 矩形:
- (void)drawRectShape:(CGContextRef)context { //CGContextStrokeRect(context, CGRectMake(40, 40, 100, 80)); // 同理(stroke 画图方法; 也有add..方法 ) CGContextAddRect(context, CGRectMake(40, 40, 100, 80)); CGContextStrokePath(context); }
- 填充:
- (void)drawFillRect:(CGContextRef)context { // 设置填充颜色 [[UIColor yellowColor] setFill]; //CGContextFillRect(context, CGRectMake(40, 40, 100, 80)); // 同理(stroke 画图方法; 也有add..方法 ) CGContextAddRect(context, CGRectMake(40, 40, 100, 80)); CGContextFillPath(context); }
- 效果:
--
-
2.5 绘制圆角矩形
-
①四角都需要圆角
/** 绘制圆角矩形 @param context 上下文(/画笔/绘图环境) @param rect 位置尺寸 @param radius 圆角半径 @param fill 是否填充,设置填充颜色在调用这个方法前设置好就行 */ - (void)drawArcRect:(CGContextRef)context Frame:(CGRect)rect Radius:(CGFloat)radius Fill:(BOOL)fill { CGFloat cor_X = rect.origin.x; CGFloat cor_Y = rect.origin.y; CGFloat cor_W = rect.size.width; CGFloat cor_H = rect.size.height; // 开始坐标右边开始 CGContextMoveToPoint(context, cor_X + cor_W, cor_Y + cor_H - radius); // 右下角角度 CGContextAddArcToPoint(context, cor_X + cor_W, cor_Y + cor_H, cor_X + cor_W - radius, cor_Y + cor_H, radius); // 左下角角度 CGContextAddArcToPoint(context, cor_X, cor_Y + cor_H, cor_X, cor_Y + cor_H - radius, radius); // 左上角 CGContextAddArcToPoint(context, cor_X, cor_Y, cor_X + radius, cor_Y, radius); // 右上角 CGContextAddArcToPoint(context, cor_X + cor_W, cor_Y, cor_X + cor_W, cor_Y + radius, radius); if (fill) { // 设置填充颜色 [[UIColor yellowColor] setFill]; // 填充 CGContextFillPath(context); } else { // 封闭路径,填充这句不写也可以 CGContextClosePath(context); // 根据坐标绘制路径 CGContextDrawPath(context, kCGPathFillStroke); } }
- 效果
- 效果
-
②单独设置圆角
/** 绘制圆角矩形 @param context 上下文(/画笔/绘图环境) @param rect 位置尺寸 @param radius 圆角半径 @param leftTop 左上角 @param rightTop 右上角 @param leftBottom 左下角 @param rightBottom 右下角 @param fill 是否填充,设置填充颜色在调用这个方法前设置好就行 */ - (void)drawArcRect:(CGContextRef)context Frame:(CGRect)rect Radius:(CGFloat)radius LeftTop:(BOOL)leftTop RightTop:(BOOL)rightTop LeftBottom:(BOOL)leftBottom RightBottom:(BOOL)rightBottom Fill:(BOOL)fill { CGFloat cor_X = rect.origin.x; CGFloat cor_Y = rect.origin.y; CGFloat cor_W = rect.size.width; CGFloat cor_H = rect.size.height; // 开始坐标右边开始 if (rightBottom) { // 开始坐标右边开始 CGContextMoveToPoint(context, cor_X + cor_W, cor_Y + cor_H - radius); CGContextAddArcToPoint(context, cor_X + cor_W, cor_Y + cor_H, cor_X + cor_W - radius, cor_Y + cor_H, radius); } else { CGContextMoveToPoint(context, cor_X + cor_W, cor_Y + cor_H); } // 左下角角度 if (leftBottom) { CGContextAddArcToPoint(context, cor_X, cor_Y + cor_H, cor_X, cor_Y + cor_H - radius, radius); } else { CGContextAddLineToPoint(context, cor_X, cor_Y + cor_H); } // 左上角 if (leftTop) { CGContextAddArcToPoint(context, cor_X, cor_Y, cor_X + radius, cor_Y, radius); } else { CGContextAddLineToPoint(context, cor_X, cor_Y); } // 右上角 if (rightTop) { CGContextAddArcToPoint(context, cor_X + cor_W, cor_Y, cor_X + cor_W, cor_Y + radius, radius); } else { CGContextAddLineToPoint(context, cor_X + cor_W, cor_Y); } if (fill) { // 填充 CGContextFillPath(context); } else { // 封闭路径,填充这句不写也可以 CGContextClosePath(context); // 根据坐标绘制路径 CGContextDrawPath(context, kCGPathFillStroke); } }
- 效果
- 效果
-
说明:
- 之所以效果图中红色顶部部分会被削掉,是因为绘制是从纵轴零点开始,而线的中心就是绘制起点,超出父控件被削掉了。
-
-
2.6 绘制椭圆
- 椭圆:
- (void)drawCircle:(CGContextRef)context { CGContextStrokeEllipseInRect(context, CGRectMake(30, 30, 200, 100)); }
- 矩形:
- (void)drawFillCircle:(CGContextRef)context { // 填充的颜色 [[UIColor yellowColor] setFill]; CGContextFillEllipseInRect(context, CGRectMake(30, 30, 100, 100)); }
- 效果:
**
-
2.7 绘制图片
- (void)drawImage { UIImage *image = [UIImage imageNamed:@"dog"]; // 位置尺寸 [image drawInRect:CGRectMake(20, 20, 100, 100)]; }
- 效果:
- 效果:
-
2.8 绘制文字
- (void)drawRect:(CGRect)rect { NSString *tempStr = @"桂林山水甲天下"; // 绘制字体大小 UIFont *textFont = [UIFont systemFontOfSize:25.f]; // 计算文字所占的size,文字居中显示在画布上 CGSize sizeText = [tempStr boundingRectWithSize:CGSizeMake(self.frame.size.width, self.frame.size.height) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:textFont} context:nil].size; CGFloat text_x = (self.frame.size.width - sizeText.width) / 2; CGFloat text_y = (self.frame.size.height - sizeText.height) / 2; CGFloat text_w = sizeText.width; CGFloat text_h = sizeText.height; CGRect textRect = CGRectMake(text_x, text_y, text_w, text_h); // 文字居中显示在画布上 NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; // 文字换行模式 paragraphStyle.lineBreakMode = NSLineBreakByCharWrapping; // 文字居中 paragraphStyle.alignment = NSTextAlignmentCenter; // 绘制文字 [tempStr drawInRect:textRect withAttributes:@{ NSFontAttributeName:textFont, NSForegroundColorAttributeName:[UIColor whiteColor], NSParagraphStyleAttributeName:paragraphStyle}]; }
- 效果:
- 效果:
-
2.8 绘制弧形、正圆、饼图
-
①绘制弧形、正圆
- (void)drawArc:(CGContextRef)context { /* * @param c 当前图形 * @param x 圆弧的中心点坐标x * @param y 圆弧的中心点坐标y * @param radius 圆弧半径 * @param startAngle 弧的起点与正X轴的夹角, * @param endAngle 弧的终点与正X轴的夹角 * @param clockwise 指定0创建一个顺时针的圆弧,或是指定1创建一个逆时针圆弧 */ //CGContextAddArc(<#CGContextRef _Nullable c#>, <#CGFloat x#>, <#CGFloat y#>, <#CGFloat radius#>, <#CGFloat startAngle#>, <#CGFloat endAngle#>, <#int clockwise#>) // 弧形 CGContextAddArc(context, 60, 60, 50, M_PI/2, 0, 1); // 线宽 CGContextSetLineWidth(context, 3); // 描线 CGContextStrokePath(context); // 画填充弧形 CGContextAddArc(context, 180, 60, 50, M_PI/2, 0, 1); // 绘制填充要记得设置填充颜色 // 设置填充颜色 [[UIColor yellowColor] setFill]; // 填充 CGContextFillPath(context); // 画圆 CGContextAddArc(context, 300, 60, 50, 2 * M_PI, 0, 1); // 设置填充颜色 [[UIColor yellowColor] setFill]; // 填充 CGContextFillPath(context); }
- 效果:
-
②饼图
/* * @context 上下文(/画笔/绘图环境) * @centerPonit 圆心 * @startAngel 弧的起点与正X轴的夹角 * @endAngle 弧的终点与正X轴的夹角 * @radius 圆弧半径 * @colorRef 圆弧填充颜色 */ - (void)drawPieChart:(CGContextRef)context Center:(CGPoint)centerPonit Start:(CGFloat)startAngel End:(CGFloat)endAngle Radius:(CGFloat)radius Color:(CGColorRef)colorRef { CGContextMoveToPoint(context, centerPonit.x, centerPonit.y); CGContextSetFillColorWithColor(context, colorRef); CGContextAddArc(context, centerPonit.x, centerPonit.y, radius, startAngel, endAngle, 0); CGContextFillPath(context); }
- 调用
#define RADIANS(x) ((x)*(M_PI)/180) // 获取弧度 [self drawPieChart:context Center:CGPointMake(100, 100) Start:0 End:RADIANS(160) Radius:70 Color:[[UIColor purpleColor] CGColor]];
- 效果
-
-
2.9 绘制贝塞尔曲线
-
①二次贝塞尔曲线
- (void)drawBezierCurve:(CGContextRef)context { // 设置Path的起点 CGContextMoveToPoint(context, 120, 200); // 设置贝塞尔曲线的控制点坐标和终点坐标 CGContextAddQuadCurveToPoint(context, 190, 210, 120, 290); // 描线 CGContextStrokePath(context); // 设置Path的起点 CGContextMoveToPoint(context, 120, 100); // 设置贝塞尔曲线的控制点坐标和终点坐标 CGContextAddQuadCurveToPoint(context, 190, 130, 120, 160); // 设置填充颜色 [[UIColor yellowColor] setFill]; // 填充 CGContextFillPath(context); }
- 效果:
-
②三次贝塞尔曲线
- (void)drawBezierCurve:(CGContextRef)context { // 设置Path的起点 CGContextMoveToPoint(context, 10, 100); //设置贝塞尔曲线的控制点坐标和控制点坐标终点坐标 CGContextAddCurveToPoint(context, 80, 20, 160, 170, 230, 60); // 描线 CGContextStrokePath(context); // 设置Path的起点 CGContextMoveToPoint(context, 10, 160); //设置贝塞尔曲线的控制点坐标和控制点坐标终点坐标 CGContextAddCurveToPoint(context, 80, 90, 160, 220, 230, 120); // 设置填充颜色 [[UIColor yellowColor] setFill]; // 填充 CGContextFillPath(context); }
- 效果:
-
-
2.10 绘制渐变颜色
-
①线性渐变
- (void)drawGradient:(CGContextRef)context { // 获取颜色空间 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); // 参与渐变的颜色 NSArray *colors = @[(__bridge id)[UIColor blueColor].CGColor, (__bridge id)[UIColor greenColor].CGColor, (__bridge id)[UIColor redColor].CGColor]; // 渐变过程 CGFloat colorIndices[] = {0.0f, 0.2f, 0.8f}; // 创建渐变属性 CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, colorIndices); // 释放颜色空间 CGColorSpaceRelease(colorSpace); // 设置渐变方向 CGPoint startPoint = CGPointMake(0, 0); CGPoint endPoint = CGPointMake(CHView_W, CHView_H); // 绘制渐变线条 CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0); // 释放渐变属性 CGGradientRelease(gradient); }
- 效果
- 效果
-
②圆形向外渐变
- (void)drawArcGradient:(CGContextRef)context { // 获取颜色空间 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); // 参与渐变的颜色 NSArray *colors = @[(__bridge id)[UIColor blueColor].CGColor, (__bridge id)[UIColor greenColor].CGColor, (__bridge id)[UIColor redColor].CGColor]; // 渐变过程 CGFloat colorIndices[] = {0.2f, 0.5f, 0.8f}; // 创建渐变属性 CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, colorIndices); // 释放颜色空间 CGColorSpaceRelease(colorSpace); // 绘制颜色渐变的圆 // 第一个参数CGContextRef:相当于画笔 // 第二个参数CGGradientRef:渐变色值,由颜色空间、渐变色数组、位置数组共同构成。 // 第三个参数startCenter:外圈的中心圆点 // 第四个参数startRadius:外圈的半径 // 第五个参数endCenter:内圈的中心圆点 // 第六个参数endRadius:内圈的半径 // 第七个参数CGGradientDrawingOptions:渐变填充选项 CGContextDrawRadialGradient(context, gradient, CGPointMake(80, 90), 10, CGPointMake(80, 90), 60, kCGGradientDrawsBeforeStartLocation); // 绘制颜色渐变的圆锥 CGContextDrawRadialGradient(context, gradient, CGPointMake(200, 90), 10, CGPointMake(300, 90), 60, kCGGradientDrawsBeforeStartLocation); }
- 效果
- 效果
-
-
2.11 绘制生成图片(图像)、保存至相册
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // 把当前视图生成一张图片 // 开启一个与当前视图一样大小的上下文尺寸 UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0); // 获取上下文(/画笔/绘图环境) CGContextRef context = UIGraphicsGetCurrentContext(); // 把当前视图绘制到上下文当中,只能通过render的渲染的方式,用draw的方法画不出效果 [self.layer renderInContext:context]; // 从上下文当中获取图片 UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); // 关闭上下文 UIGraphicsEndImageContext(); // 保存图片到相册 // 第一个参数为需要保存的图片。 // 第二个参数是保存完成后回调的目标对象。 // 第三个是保存完成后回调到目标对象的哪个方法中,固定格式,修改会报错。 // 第四个参数在保存完成后,会原封不动地传回到回调方法的contextInfo参数中。 UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), (__bridge void *)self); } // 必要实现的协议方法, 不然会崩溃 - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo { CHLog(@"保存成功"); }
- 保存至相册需要在Info.plist添加相册访问权限:需要使用到相册,用于模拟屏幕截图
- 保存至相册需要在Info.plist添加相册访问权限:需要使用到相册,用于模拟屏幕截图
-
2.12 绘制阴影
- (void)drawRect:(CGRect)rect { // 获取上下文(/画笔/绘图环境) CGContextRef context = UIGraphicsGetCurrentContext(); // 设置填充颜色 [[UIColor yellowColor] setFill]; //颜色转化,Quartz 2d必须使用转换后的颜色 CGColorRef color = [UIColor blueColor].CGColor; /*设置阴影 context:图形上下文 offset:偏移量 blur:模糊度 color:阴影颜色 */ CGContextSetShadowWithColor(context, CGSizeMake(10, 10), 0.9, color); // 同理(stroke 画图方法; 也有add..方法 ) CGContextAddRect(context, CGRectMake(40, 40, 100, 80)); CGContextFillPath(context); }
- 显示效果
- 显示效果
-
2.14 拼接路径:实现共有部分路径绘制,部分路径填充
- (void)drawRect:(CGRect)rect { // 获取上下文(/画笔/绘图环境) CGContextRef context = UIGraphicsGetCurrentContext(); // 创建可变路径 CGMutablePathRef path = CGPathCreateMutable(); // 线段路径 // 设置起点 CGPathMoveToPoint(path, NULL, 0, 0); // 画到 CGPathAddLineToPoint(path, NULL, 150, 50); // 画到 CGPathAddLineToPoint(path, NULL, 75, 100); // 线条颜色 [[UIColor redColor] setStroke]; // 线条的宽度 CGContextSetLineWidth(context, 5); // 添加路径 CGContextAddPath(context, path); // 绘制粗线 CGContextDrawPath(context, kCGPathStroke); // 01 添加路径:线条专用 CGContextAddPath(context, path); // 绘制粗线 CGContextDrawPath(context, kCGPathStroke); // 左下角 CGPathAddLineToPoint(path, NULL, 0, 90); // 线条颜色 [[UIColor colorWithRed:0 green:1.0 blue:0 alpha:0.5] setFill]; // 02 添加路径:填充专用 CGContextAddPath(context, path); // 填充 CGContextDrawPath(context, kCGPathFill); // 释放路径 CGPathRelease(path); }
- 显示效果
- 显示效果
-
2.15 剪裁往后绘制超出的部分
- (void)drawRect:(CGRect)rect { // 获取上下文(/画笔/绘图环境) CGContextRef context = UIGraphicsGetCurrentContext(); /* * @param c 当前图形 * @param x 圆弧的中心点坐标x * @param y 圆弧的中心点坐标y * @param radius 圆弧半径 * @param startAngle 弧的起点与正X轴的夹角, * @param endAngle 弧的终点与正X轴的夹角 * @param clockwise 指定0创建一个顺时针的圆弧,或是指定1创建一个逆时针圆弧 */ //CGContextAddArc(<#CGContextRef _Nullable c#>, <#CGFloat x#>, <#CGFloat y#>, <#CGFloat radius#>, <#CGFloat startAngle#>, <#CGFloat endAngle#>, <#int clockwise#>) // 画圆 CGFloat centerX = self.frame.size.width / 2; CGFloat centerY = self.frame.size.height / 2; CGFloat radius = centerX; // // 裁剪路径 // CGRect clipRect = CGRectMake(0, 0, self.frame.size.width / 2, self.frame.size.height); // // 剪裁往后绘制超出的部分 // UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:clipRect]; // CGContextAddPath(context, clipPath.CGPath); // CGContextClip(context); CGContextAddArc(context, centerX, centerY, radius, 2 * M_PI, 0, 1); // 设置填充颜色 [[UIColor yellowColor] setFill]; // 填充 CGContextFillPath(context); }
- 显示效果对比
**
- 显示效果对比
-
2.16 擦除
- (void)drawRect:(CGRect)rect { [super drawRect:rect]; // 获取上下文(/画笔/绘图环境) CGContextRef context = UIGraphicsGetCurrentContext(); // 擦干净画板 CGContextClearRect(context, self.bounds); }
- 应用场景说明:每次需要进行重绘的时候,为了避免上一次绘制的保留内容对本次的影响,应全部擦除画板。类似于擦黑板,也可以在绘制时部分擦除。