Quartz 2D是一个二维绘图引擎,同时支持iOS和Mac系统。
使用Quartz 2D可以完成以下事情:
- 绘制图形:线条/三角形/矩形/圆/弧等
- 绘制文字
- 绘制/生成图片
- 读取/生成PDF
- 截图/裁剪图片
- 自定义UI控件
图表/涂鸦/画板/手势解锁等比较个性化功能使用系统提供的UI控件无法实现,可以利用Quartz 2D把控件的结构画出来,代码执行效率高,而且非常灵活。其实,iOS中大部分控件的内容都是通过Quartz 2D画出来的。
一、初识上下文
图形上下文(Graphics Context)是一个CGContextRef
类型的数据。可以理解为一个画布,所有绘画操作都是在画布上进行的。
作用:
- 保存绘图信息、绘图状态;
- 决定绘制的内容输出形式是什么样子的(输出形式可以是PDF、Bitmap、Window、Layer等)。
1.1. 自定义view
1.1.1. 如何利用Quartz 2D自定义view?
- 首先,需要有图形上下文,因为它能保存绘图信息,并且决定着绘制到什么地方去;
- 其次,那个图形上下文必须跟view相关联,才能将内容绘制到view上面。
1.1.2. 自定义view的步骤
- 新建一个类,继承自
UIView
; - 在
- (void)drawRect:(CGRect)rect
方法内部:- 取得跟当前view相关联的图形上下文(该方法内部会自动创建和当前view相关联的上下文对象,可以直接获取。无论是创建还是获取,上下文都是以
UIGrapnics
开头); - 开始绘制相应的图形内容;
- 利用图形上下文将绘制的所有内容渲染显示到view上面;
- 参数
rect
是当前view
的bounds
。
- 取得跟当前view相关联的图形上下文(该方法内部会自动创建和当前view相关联的上下文对象,可以直接获取。无论是创建还是获取,上下文都是以
每次新建view类时,系统都会在实现类中默认创建drawRect
代码,并且给出相应的使用说明:
1 | /* |
如果需要自定义绘制,只需要重写
drawRect:
方法即可。如果重写了该方法但是没有实现任何内容,在动画期间会影响性能。
drawRect:
方法调用时机:
- 调用时机在
viewWillAppear
和viewDidAppear
之间 - 当
view
的size
不为0时调用setNeedsDisplay
或setNeedsDisplayInRect:
,会触发drawRect:
- 调用
sizeToFit
,会触发drawRect:
UIView
的contentMode
属性设置成UIViewContentModeRedraw
,每一次设置或更改frame
都会触发drawRect:
二、UIBezierPath(贝塞尔曲线)
2.1. 基本线条(直线)
空界面:
场景:新建DBDrawView: UIView
,实现drawRect:
方法:在界面上绘制不同颜色的线条。
1 | - (void)drawRect:(CGRect)rect { |
示例效果:
设置线条宽度、颜色
1 | // 设置上下文状态(线宽、颜色等) |
设置颜色有三种形式:
[[UIColor orangeColor] setStroke];
对应绘制渲染CGContextStrokePath(ctx)
,否则无效;[[UIColor orangeColor] setFill];
对应绘制渲染CGContextFillPath(ctx)
,否则无效;[[UIColor orangeColor] set];
自动匹配渲染样式。
上面绘制直线的时候已经看到UIBezierPath
,但他的功能不仅仅是画直线。可以说只要用到自定义绘制,大概率都会用到UIBezierPath
,甚至一些动画也是按照指定的曲线路径运动的。
注意:只要是在view上自定义绘制内容,必须在drawRect
执行,否则获取不到上下文。
2.2. 曲线
曲线由两个端点、一个控制点构成,端点决定了曲线的范围,控制点决定了曲线的曲率。
1 | - (void)drawRect:(CGRect)rect { |
2.3. 矩形
1 | - (void)drawRect:(CGRect)rect { |
注意:宽度和高度相等时就是正方形。
2.4. 圆角矩形
1 | - (void)drawRect:(CGRect)rect { |
可以指定边角是否加圆角
1 | - (void)drawRect:(CGRect)rect { |
注意:cornerRadii
出入的size
是以宽度为准的,高度可以忽略。
2.5. 椭圆
1 | - (void)drawRect:(CGRect)rect { |
当传入的Rect
宽度和高度相等的时候,就是圆形
1 | UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(50.0, 50.0, 100.0, 100.0)]; |
2.6. 简洁写法
上面的示例中,都必须手动编写上下文并设置对应状态。其实UIBezierPath
可以直接设置相关状态,不需要编写上下文的代码(内部已经帮忙实现)。
2.6.1. 描边
1 | - (void)drawRect:(CGRect)rect { |
2.6.2. 填充
1 | - (void)drawRect:(CGRect)rect { |
注意:所有设置必须写在[path stroke]
或[path fill]
前面。
三、弧和扇形的绘制
3.1. 弧
弧的本质其实是圆的一部分,所以弧和圆的圆心及半径有直接关系。
UIBezierPath
提供了一个绘制弧路径的方法:
1 | /* |
示例:
1 | - (void)drawRect:(CGRect)rect { |
上面所有参数不变,只把clockwise
换成顺时针,看下效果:
3.2. 扇形
扇形就是弧度路径结束后,添加一条到圆心的直线,关闭路径即可。
1 | - (void)drawRect:(CGRect)rect { |
核心代码:
1 | // 扇形:添加一条到圆心的线 |
把填充方式换成fill
:
1 | [path fill]; |
注意:如果使用的是fill
,会自动关闭路径,可以不写[path closePath]
。
四、案例
4.1. 圆形进度条
ViewController.m
1 |
|
DBCircleProgressView
1 | // DBCircleProgressView.h |
视图层次
效果
4.2. 扇形饼图
ViewController.m
1 | @interface ViewController () |
DBSectorView
1 | @implementation DBSectorView |
效果
五、drawRect方法的其他用法
drawRect还可以绘制文字、图片等,只要能显示的任何UI内容,都可以通过drawRect绘制。
5.1. 绘制文字
DBDrawTextView
1 | - (void)drawRect:(CGRect)rect { |
效果:
注意:设置NSStrokeColorAttributeName
后,NSForegroundColorAttributeName
就会失效。
5.2. 绘制图片
DBDrawImageView
1 | @implementation DBDrawImageView |
效果:
六、深入理解上下文(状态栈)
抽象上来说上下文分为两块区域,一块区域存放路径,另一块区域存放状态。每次渲染都是从存放路径的区域取出路径,并应用当前状态。上下文状态默认只有一份,为了能够有多种状态可重复利用,就有了上下文状态栈。上下文状态栈可以保存和恢复对应的状态,是一个栈结构,每保存一次相当于向栈中添加一次状态,恢复一次就是把栈顶的状态取出并应用。
小技巧:上下文状态栈也可以理解为历史记录。
保存当前上下文状态:
1 | CGContextSaveGState(ctx); |
取出(恢复)上下文状态栈中栈顶状态:
1 | CGContextRestoreGState(ctx); |
示例:
1 | - (void)drawRect:(CGRect)rect { |
效果:
扩展-上下文的矩阵操作
上下文内容在添加到渲染进程中前可以对内容进行一些矩阵变换(形变)。
CGContextTranslateCTM
平移CGContextScaleCTM
缩放CGContextRotateCTM
旋转
注意:上下文中的矩阵变换坐标系和UIView中的坐标系不一样。