记录寄己走过的路

iOS 常用绘图—「drawRect绘制矩形」

Write in the first


关于绘图,其实在我当前接触的项目中用得很少,但是以后肯定会接触到。
像这种不常用但比较重要的内容,我觉得要做到初步理解,然后梳理总结,到最后夯实基础、活学活用。所以写这篇文章。

  编码无处不在

iOS 系统本身提供了两大绘图的框架,即 UIKit 和 Core Graphics。
UIKit:像 UIImage(绘制图像)、NSString(绘制文本)、UIBezierPath(绘制形状)、UIColor。 这些类提供了功能有限但使用方便的方法来让我们完成绘图任务。一般情况下,UIKit就是我们所需要的。

Core Graphics:CoreGraphics也称为Quartz 2D 是UIKit下的主要绘图系统,频繁的用于绘制自定义视图。Core Graphics是高度集成于UIView和其他UIKit部分的。Core Graphics数据结构和函数可以通过前缀CG来识别。

本篇文章主要从【iOS绘图 drawRect】学习总结,
在「时间 & 知识 」有限内,总结的文章难免有「未全、不足 」的地方,还望各位好友指出,以提高文章质量。

目录:

  1. 绘图基本概念
  2. 我是Core Graphics绘图
  3. UIBezierPath绘图概念
  4. 我是UIKit 之 UIBezierPath绘图
  5. 期待 & 后续

绘图基本概念

在学习绘图之前,我们先来了解一下几个基本的概念

属性 描述
drawRect:(CGRect)rect 作用:专门用来绘图
什么时候调用:当View显示的时候调用(ViewWillAppear和ViewDidAppear之间)
参数rect:当View的bounds,Note: 在drawRect方法当中系统已经帮你创建一个跟View相关联的上下文(Layer上下文),只要获取上下文就可以了
什么操作调用:1、如果在UIView初始化时没有设置rect大小,将直接导致drawRect不被自动调用。2、该方法在调用sizeThatFits后被调用,所以可以先调用sizeToFit计算出size。然后系统自动调用drawRect:方法。3、通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:。4、直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:,但是有个前提条件是rect不能为0。
Context 定义:图形上下文,是一个CGContextRef类型的数据,你可以将图形上下文理解为一块画布
作用:1、保存绘图信息,绘图状态;2、决定绘制的输出目标(绘制到什么地方去?// 输出目标可以是PDF文件、Bitmap或者显示器的窗口上)3、相同的一套绘图序列,指定不同的 Graphics Context,就可将相同的图像绘制到不同的目标上
path 路径,ios绘图可以想象为你拿着一支笔去画图,画几条线或几个点从而形成一个路径,之后可以利用理解去填色或者描边
stroke、fill 描边和填充,每个路径都需要填充或者描边后才能在视图中看见,他们都各自有很多样式可以设置,常见的有颜色、粗细、渐变,连接样式等等。

了解完上面的几个概念之后,我想 我们先不着急写的,首先你的知道你想实现什么效果用到什么属性值,及对属性值意思的理解。然后再结合下面的代码 就更容易理解和吸收了

Core Graphics绘图属性 描述
UIGraphicsGetCurrentContext() 获取上下文
CGContextMoveToPoint(ctx, x, y) 设置起始点
CGContextAddLineToPoint(ctx, x, y) 添加一根线到终点
CGContextAddLines(ctx, points, count) 添加线(点之间) 参数:points点数组,count数组个数
CGContextAddEllipseInRect(ctx, CGRect rect) 画椭圆,如果长宽相等就是圆
CGContextAddRect(ctx, CGRect rect) 画矩形,长宽相等就是正方形
[image drawInRect:CGRect rect] 画图片
[@”hello world” drawInRect:CGRect rect withAttributes:NSDictionary *dict] 画文字 (属性值:NSFontAttributeName字体大小、NSForegroundColorAttributeName字体颜色)
CGContextAddArc(ctx, CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise) 画圆 参数:圆心的x坐标,圆心的y坐标,圆的半径,开始弧度,结束弧度,0表示顺时针 1表示逆时针
CGContextAddQuadCurveToPoint(ctx, CGFloat cpx, CGFloat cpy, CGFloat x, CGFloat y); 二次曲线函数 参数:控制点 x坐标,控制点 y坐标,终点 x坐标,终点 y坐标 Note:这里使用必须要设置起点MoveToPoin
CGContextAddCurveToPoint(ctx, CGFloat cp1x, CGFloat cp1y, CGFloat cp2x, CGFloat cp2y, CGFloat x, CGFloat y) 三次曲线函数 参数:控制点1 x y坐标,控制点2 x y坐标,终点 x坐标,终点 y坐标 Note:这里使用必须要设置起点MoveToPoin
CGContextStrokePath(ctx) 渲染上下文(Stroke描边,空心)
CGContextFillPath(ctx) 渲染上下文(Fill填充,实心)
CGContextDrawPath(ctx, CGPathDrawingMode mode) 渲染上下文 mode:1、kCGPathFill 只有填充(非零缠绕数填充),不绘制边框 2、kCGPathEOFill 奇偶规则填充(多条路径交叉时,奇数交叉填充,偶交叉不填充)3、kCGPathStroke 只有边框 4、kCGPathFillStroke 既有边框又有填充 5、kCGPathEOFillStroke 奇偶填充并绘制边框
其他属性设置
CGContextSetStrokeColorWithColor(ctx, Color) 设置线颜色
CGContextSetFillColorWithColor(ctx, Color) 设置填充色
CGContextSetLineWidth(ctx, CGFloat width); 设置线宽度
CGContextSetLineJoin(ctx, CGLineJoin join) 设置连接样式(kCGLineJoinMiter尖的、斜接, Round圆, Bevel斜面)
CGContextSetLineCap(ctx, CGLineCap cap) 设置顶角样式(kCGLineCapButt, Round, Square)
CGContextSetLineDash(ctx, CGFloat phase, lengths, count) 设置虚线样式(参数:phase虚线从那开始绘制;lengths C语言的数组,举个例子: 声明一个数组 CGFloat dash[] = @{3.0, 1.0}意思就是长度为3.0 间隙长度为1.0,以此类推;count lengths数组的个数)

示例代码:自定义View的步骤
新建一个类,继承UIView
实现- (void)drawRect:(CGRect)rect方法

我是Core Graphics绘图

1、获取上下文
2、绘制路径
3、添加路径到上下文
4、渲染上下文(描边或填充)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
- (void)drawRect:(CGRect)rect {
//------------------我是Core Graphics绘图-------------
    // 获取ctx
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    // 设置绘图的其他属性
    // 设置线颜色
    CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);
    // 设置线宽度
    CGContextSetLineWidth(ctx, 5);
    // 设置线颜色
    [[UIColor redColor] set];
    // 设置填充色
    CGContextSetFillColorWithColor(ctx, [UIColor purpleColor].CGColor);
    // 设置连接样式(Miter尖的、斜接, Round圆, Bevel斜面)
    CGContextSetLineJoin(ctx, kCGLineJoinRound);
    // 设置顶角样式(Butt, Round, Square)
    CGContextSetLineCap(ctx, kCGLineCapRound);
    // 设置虚线样式
    CGFloat lengths[] = { 20, 5 };
    CGContextSetLineDash(ctx, 0, lengths, 2);
    // 画线
    [self drawLine:ctx];
    // 画矩形,画椭圆,多边形
    [self drawSharp:ctx];
    // 画图片
    [self drawPicture:ctx];
    // 画文字
    [self drawText:ctx];
    // 画圆、画弧
    [self drawCircle:ctx];
}

PS.
第一个方法写的比较详细,写了使用直接画线的方式 和 path的方式。推荐使用path的方式画线。 另外,第一个方法也写了移动笔触画线和用点集合画线。后面方法只会涉及其中一种,因为方法都比较类似

画线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 画线
-(void)drawLine:(CGContextRef)ctx{
    // 画一条简单的线
    CGPoint points1[] = {CGPointMake(10, 80),CGPointMake(300, 80)};
    CGContextAddLines(ctx,points1, 2);
    // 画线方法一:使用CGContextAddLineToPoint画线,需要先设置一个起始点
    // 设置起始点
    CGContextMoveToPoint(ctx, 10, 100);
    // 添加一根线到终点
    CGContextAddLineToPoint(ctx, 100,120);
    // 再添加一根线到终点,变成折线
    CGContextAddLineToPoint(ctx, 150, 100);
    // 画线方法二:
    // 构造线路径的点数组
    CGPoint points2[] = {CGPointMake(10, 130),CGPointMake(10, 160),CGPointMake(130, 200)};
    CGContextAddLines(ctx,points2, 3);
    
    // 利用路径去画一组点(推荐使用路径的方式,虽然多了几行代码,但是逻辑更清晰了)
    // 第一个路径
    CGMutablePathRef path1 = CGPathCreateMutable();
    CGPathMoveToPoint(path1, &CGAffineTransformIdentity, 0, 200);
    //CGAffineTransformIdentity 类似于初始化一些参数
    CGPathAddLineToPoint(path1, &CGAffineTransformIdentity, 100, 250);
    CGPathAddLineToPoint(path1, &CGAffineTransformIdentity, 310, 210);
    //路径1加入context
    CGContextAddPath(ctx, path1);
    // path同样有方法CGPathAddLines(),和CGContextAddLines()
    // 渲染上下文
    CGContextStrokePath(ctx);
}

画矩形,画椭圆,多边形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 画矩形,画椭圆,多边形
-(void)drawSharp:(CGContextRef)ctx{
    CGContextSetFillColorWithColor(ctx, [UIColor redColor].CGColor);
    // 画椭圆,如果长宽相等就是圆
    CGContextAddEllipseInRect(ctx, CGRectMake(0, 250, 50, 50));
    // 画矩形,长宽相等就是正方形
    CGContextAddRect(ctx, CGRectMake(70, 250, 50, 50));
    // 画多边形,多边形是通过path完成的
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, &CGAffineTransformIdentity, 120, 250);
    CGPathAddLineToPoint(path, &CGAffineTransformIdentity, 200, 250);
    CGPathAddLineToPoint(path, &CGAffineTransformIdentity, 180, 300);
    CGPathCloseSubpath(path);
    CGContextAddPath(ctx, path);
    // 填充
    CGContextFillPath(ctx);
}

画图片、画文字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 画图片
-(void)drawPicture:(CGContextRef)context{
    UIImage *image = [UIImage imageNamed:@"阿狸头像"];
    [image drawInRect:CGRectMake(10, 300, 100, 100)];
    //CGContextDrawImage(ctx, rect, image.CGImage);
}
// 画文字
-(void)drawText:(CGContextRef)ctx{
    NSDictionary *dict = @{NSFontAttributeName:[UIFont systemFontOfSize:18],
                           NSForegroundColorAttributeName:[UIColor redColor]};
    [@"hello world" drawInRect:CGRectMake(120 , 350, 500, 50) withAttributes:dict];
}

画圆、圆弧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 画圆、圆弧
-(void)drawCircle:(CGContextRef)ctx{
    CGContextSetStrokeColorWithColor(ctx, [UIColor purpleColor].CGColor);
    /**
绘制路径方法一:
CGContextRef c,// 上下文
CGFloat x, // 圆心的x坐标
CGFloat y, // 圆心的y坐标
  CGFloat radius, // 圆的半径
CGFloat startAngle, // 开始弧度
CGFloat endAngle, // 结束弧度
int clockwise // 0表示顺时针,1表示逆时针
*/
    // 圆
    CGContextAddArc (ctx, 100, 100, 50, 0, M_PI* 2 , 0);
    CGContextFillPath(ctx);
    // 半圆
    CGContextAddArc (ctx, 100, 200, 50, 0, M_PI, 0);
    CGContextStrokePath(ctx);
    /**
三次曲线函数
CGContextRef c,
CGFloat cp1x, // 控制点1 x坐标
CGFloat cp1y, // 控制点1 y坐标
CGFloat cp2x, // 控制点2 x坐标
CGFloat cp2y, // 控制点2 y坐标
     CGFloat x,  // 直线的终点 x坐标
     CGFloat y  // 直线的终点 y坐标
     */
    CGContextMoveToPoint(ctx, 200, 200);
    CGContextAddCurveToPoint(ctx, 200, 0, 300, 200, 400, 100);
    CGContextStrokePath(ctx);
    /**
     二次曲线函数
     CGContextRef c,
     CGFloat cpx,  //控制点 x坐标
     CGFloat cpy,  //控制点 y坐标
     CGFloat x,  //直线的终点 x坐标
     CGFloat y  //直线的终点 y坐标
     */
    CGContextMoveToPoint(ctx, 100, 100);
    CGContextAddQuadCurveToPoint(ctx, 200, 0, 300, 150);
    CGContextStrokePath(ctx);
}

Core Graphics绘图效果:
Core Graphics绘图.png

UIBezierPath绘图概念

同样,首先你的知道你想实现什么效果用到什么属性值,及对属性值意思的理解。然后再结合下面的代码 就更容易理解和吸收了

UIKit 之 UIBezierPath绘图属性 描述
[UIBezierPath bezierPath] 创建路径
moveToPoint:(CGPoint) 设置起点
addLineToPoint:(CGPoint) 添加一根线到终点
[[UIColor redColor] set] 设置线颜色(注意:如果使用 setStroke 和 setFill 与渲染方式要相对应)
addQuadCurveToPoint:(CGPoint) controlPoint:(CGPoint) 画曲线(controlPoint:弯曲方向点)
bezierPathWithRect:(CGRect) 画矩形
bezierPathWithRoundedRect:(CGRect) cornerRadius:(CGFloat) 画圆角矩形
bezierPathWithOvalInRect:(CGRect) 画圆(Width = Height)、画椭圆(Width != Height)
bezierPathWithArcCenter:(CGPoint) radius:(CGFloat) startAngle:(CGFloat) endAngle:(CGFloat) clockwise:(BOOL) Center:弧所在的圆心(这里不能直接用self.center,因为它是相对于它的父控件的,采用rect 宽度0.5、高度0.5),radius:圆的半径,startAngle:开始角度, endAngle:结束角度,clockwise:YES顺时针 NO逆时针
bezierPathWithArcCenter:(CGPoint) radius:(CGFloat) startAngle:(CGFloat) endAngle:(CGFloat) clockwise:(BOOL) [path addLineToPoint:center]; 注:1、[path closePath] 从路径终点连接一根线到路径的起点,2、[path fill] 填充之前,会自动关闭路径
UIGraphicsBeginImageContextWithOptions 开启上下文(size:上下文大小, opaque:透明度(YES透明,NO不透明), scale:图片质量,一般为0和当前设备的分辨率一样)
drawAtPoint:(CGPoint) 图片绘制到上下文
addClip 设置成裁剪区域
drawAtPoint:(CGPoint) blendMode:(CGBlendMode) alpha:(CGFloat) 图片绘制到上下文
drawAtPoint:(CGPoint) withAttributes:NSDictionary *dict) 文字绘制到上下文(属性值:NSFontAttributeName字体大小、NSForegroundColorAttributeName字体颜色)
UIGraphicsEndImageContext() 关闭上下文
[path stroke] / [path fill] 底层做了:1.获取上下文->2.绘制路径->3.添加路径到上下文->4.渲染上下文

PS.
同样第一个方法写的比较详细,且有注释
实现- (void)drawRect:(CGRect)rect方法

我是UIKit 之 UIBezierPath绘图

1、获取上下文
2、绘制路径
3、添加路径到上下文
4、渲染上下文(描边或填充)

1
2
3
4
5
6
7
- (void)drawRect:(CGRect)rect {
//------------------我是UIKit 之 UIBezierPath绘图-------------
    [self drawLine];// 画直线、曲线
    [self drawRect];// 画矩形、画椭圆 
    [self drawRidan];// 画弧度、扇形

画直线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/** 画直线、曲线 */
- (void)drawLine{
    // 1.获取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    // 2.绘制路径
    UIBezierPath *path = [UIBezierPath bezierPath];
    // 设置起点 + 添加一根线到终点
    [path moveToPoint:CGPointMake(10, 150)];
    [path addLineToPoint:CGPointMake(160, 80)];
    // 画第二条线
    [path addLineToPoint:CGPointMake(130, 150)];
    // 画曲线
    [path moveToPoint:CGPointMake(10, 280)];
    // 画曲线(controlPoint:弯曲方向点)
    [path addQuadCurveToPoint:CGPointMake(180, 280) controlPoint:CGPointMake(130, 130)];
    // **在这里设置其他属性与上面的一样就不在写一遍了**
    
    // 3.路径添加到上下文
    // UIBezierPath:UIKit框架 ,CGPathRef:CoreGraphics框架
    CGContextAddPath(ctx, path.CGPath);
    // 4.渲染上下文
    CGContextStrokePath(ctx);// 描边(空心)
    //CGContextFillPath(ctx);// 填充(实心)
}

画矩形、椭圆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/** 画矩形、椭圆 */
- (void)drawRect{
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    // 画矩形
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(10, 300, 80, 40)];
    // 画圆角矩形
    UIBezierPath *path1 = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(100, 300, 80, 40) cornerRadius:10];
    // 画指定角为圆角的矩形
    UIBezierPath *path2 = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(200, 300, 80, 40) byRoundingCorners:UIRectCornerBottomRight cornerRadii:CGSizeMake(10, 10)];
    [[UIColor redColor] set];
    CGContextAddPath(ctx, path.CGPath);
    CGContextAddPath(ctx, path1.CGPath);
    CGContextAddPath(ctx, path2.CGPath);
    CGContextStrokePath(ctx);
    // 使用UIBezierPath提供的绘图方法进行绘制
    // 画椭圆、圆
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(10, 370, 180, 50)];
    // 使用UIBezierPath提供的绘图方法进行绘制
    [path stroke];// 底层做了:1.获取上下文->2.绘制路径->3.添加路径到上下文->4.渲染上下文
}

画弧度、扇形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/** 画弧度、扇形 */
- (void)drawRidan{
    CGPoint center = CGPointMake(100, 430);
    CGFloat radius = 100;
    // 画弧度 clockwise:方向(顺时针或是逆时针)
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0 endAngle:M_PI_4*3 clockwise:YES];
    
    // 画扇形
    [path addLineToPoint:center];
    //[path closePath];// 从路径终点连接一根线到路径的起点
    [path fill];// fill填充之前,会自动关闭路径
    //[path stroke];
}

画图片和文字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
 根据传入的图片,生成一终带有边框的圆形图片
 参数:边框宽度,边框颜色,原始图片
 */
+ (UIImage *)imageWithImage:(UIImage *)image Border:(CGFloat)borderW color:(UIColor *)borderColor {
    CGSize size = CGSizeMake(image.size.width + 2*borderW, image.size.height + 2*borderW);
    // 1.开启一个跟图片原始大小的上下文
    UIGraphicsBeginImageContextWithOptions(size, NO, 0);
    // 2.绘制大圆
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, size.width, size.height)];
    [borderColor set];// 边框颜色
    [path fill];
    // 设置圆形裁剪区域
    UIBezierPath *clipPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(borderW, borderW, image.size.width, image.size.height)];
    [clipPath addClip];
    // 3.把图片绘制到上下文当中
    [image drawAtPoint:CGPointMake(borderW, borderW)];
    // 3.把文字绘制到上下文当中
    NSDictionary *dict = @{NSFontAttributeName:[UIFont systemFontOfSize:18.f],NSForegroundColorAttributeName:[UIColor whiteColor]};
    [@"我是绘制上的\n图片和文字" drawAtPoint:CGPointMake(0, 0) withAttributes:dict];
    // 4.从上下文当中生成一张图片
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    
    // 5.关闭上下文
    UIGraphicsEndImageContext();
    image = newImage;
    return image;
}

效果:
UIBezierPath绘图.gif

如果你想更深入学习 UIBeizerPath ,这里有一篇官方译文加原理的总结 ,👍推荐一篇 UIBezierPath译文+活用

期待


  • 如果在阅读过程中遇到 error || new ideas,希望你能 messages 我,我会及时改正谢谢。
  • 点击右上角的 喜欢 和 订阅Rss 按钮,可以收藏本仓库,并在 Demo 更新时收到邮件通知。
❄︎ 本文结束    感谢简阅 ^_^. ❄︎

本文标题:iOS 常用绘图—「drawRect绘制矩形」

文章作者:寄己的路

原始链接:https://sunyonghui.github.io/iOSNET/drawRect.html

版权声明: 署名-非商业性使用-禁止演绎 4.0 国际 本博客所有文章除特别声明外均为原创,转载务必请「注明出处链接(可点击) - 作者」,并通过E-mail等方式告知,谢谢合作!