最近都是对UI部分的研究,特别是Quartz2D部分。将会有一波相关文章来袭~~~今天记录的是绘制路径和绘制文字。
一. Quartz 2D简介:
什么是Quartz 2D?
Quartz 2D是一个二维绘图引擎,支持iOS和mac系统。
Quartz 2D可以做的事情:
- 绘制图形: 线条、三角形、矩形、圆形、椭圆、弧形、扇形
- 绘制文字
- 绘制/生成图片(图像)
- 读取/生成Pdf
- 截图/剪裁图片
- 自定义UI控件:虽然UIKit框架提供的控件可以实现UI界面,但是有些极其复杂、个性化的空间需要Quartz 2D才能画出来
例如:
-
削圆
绘图
-
做手势解锁
-
做折线图、饼状图、矩阵图(做复杂的我们可以使用Charts 这个第三方)
二.图形上下文
在开始绘制之前,我们需要先知道图形上下文。
- 图形上下文(Graphics Context):是一个
CGContextRef
类型的数据类型;
我们可以把它理解为画板 - 作用:
- 保存绘图信息、绘图状态
- 决定绘制的输出目标(绘制到什么地方,例如:PDF文件、Bitmap或者显示器的窗口上)
相同的一套绘图序列,指定不同的Graphic Context,就可以将相同的图像绘制到不同的目标
- Quartz 2d提供的Graphics Context(图形上下文):
Bitmap Graphics Context
PDF Graphics Context
Window Graphics Context
Layer Graphics Context
Printer Graphics Context
三.路径绘制
绘图顺序
在view上面绘图需要做一下操作:
- 自定义view,在view的- (void)drawRect:(CGRect)rect方法里面获取图形上下文;
当view显示的时候调用 viewwillapper之后,didapper之前会调用此方法;
刷新视图,调用view的setNeedsDisplay方法的时候;
//作用:专门用来绘图的
//什么时候调用:当view显示的时候调用 viewwillapper之后,didapper之前
//参数:当前view的bounds
- (void)drawRect:(CGRect)rect{
}
- 获取当前跟view相关的图形上下文
- 描述路径
- 把路径添加到上下文
- 把上下文当中绘制的所有路径渲染到view的layer中
绘图前提
我们下面开始绘制路径,所有的操作都基于这部分:
在Main.storyboard的viewcontroller里面添加一个DrawView,所有的操作将在这个DrawView上面进行
总结:
直线、曲线调用实例方法绘制
矩形、圆形、椭圆调用类方法绘制
弧形、扇形调用实例、类方法都可以绘制
//直线
//生成路径
+ (instancetype)bezierPath;
//设置起点
- (void)moveToPoint:(CGPoint)point;
//设置直线终点
- (void)addLineToPoint:(CGPoint)point;
//曲线
//controlPoint是拐弯的点
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;
//矩形
//rect:矩形的frame x,y,宽、高
+ (instancetype)bezierPathWithRect:(CGRect)rect;
//圆形
//描述圆角矩形 cornerRadius:圆角半径
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius; // rounds all corners with the same horizontal and vertical radius
//椭圆
rect :x,y,横直径、纵直径
+ (instancetype)bezierPathWithOvalInRect:(CGRect)rect;
//弧形、扇形
//ArcCenter:所在圆的圆心
//radius:半径
//startAngle:开始角度;0度是在圆的最右侧;向上:度数是负的;向下:度数是正的
//endAngle:截止角度(到哪个位置)
//clockwise:是否为顺时针(怎么到达)
方法1:
+ (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
方法2:
- (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise NS_AVAILABLE_IOS(4_0);
1.绘制直线
- 效果图
我们要绘制的效果,添加两条直线:
- 主要使用方法
//生成路径
+ (instancetype)bezierPath;
//设置起点
- (void)moveToPoint:(CGPoint)point;
//设置终点
- (void)addLineToPoint:(CGPoint)point;
1.1 绘制方法1
在drawRect方法内部会自动创建一个跟view相关联的上下文,所以我们在view的drawRect进行绘制;
- (void)drawRect:(CGRect)rect {
//在drawRect方法内部会自动创建一个跟view相关联的上下文
//可以直接获取
//无论开启上下文,还是获取上下文,都是UIGraphics
//1.获取当前跟view相关的图形上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
//2.描述路径:一个路径可以描述多条线
UIBezierPath * path = [UIBezierPath bezierPath];
//2.1设置起点
//坐标原点是以当前绘制view的左上角为(0,0)
[path moveToPoint:CGPointMake(10, 10)];
//2.2添加一根线到某个点
[path addLineToPoint:CGPointMake(100, 100)];
//再添加一根线
[path moveToPoint:CGPointMake(20, 10)];
[path addLineToPoint:CGPointMake(200, 100)];
//3.把路径添加到上下文
CGContextAddPath(ctx, path.CGPath);
//4.把上下文当中绘制的所有路径渲染到view的layer中
//渲染的方式有两种:
//stroke:描边
//fill:填充
CGContextStrokePath(ctx);
}
注意
渲染到view上面的方法有
CGContextStrokePath(ctx);//描边写法
CGContextFillPath(ctx);//填充写法
1.2 绘制方法2
上面我们要获取图形上下文,把路径添加到上下文,然后把绘制的路径渲染到view的layer中。我们还可以把上面的步骤简单化,按下面这种方式写:
- (void)drawRect:(CGRect)rect {
UIBezierPath * path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(10, 10)];
[path addLineToPoint:CGPointMake(100, 100)];
[path moveToPoint:CGPointMake(20, 10)];
[path addLineToPoint:CGPointMake(200, 100)];
//把绘制的图形添加到上下文、并且渲染到view的layer层上
[path stroke];
}
注意
同样的,这一种绘制方法里面,渲染的方法由两种,但是填充写法都是在图形上面才有填充效果,例如圆形、扇形,后面我们再演示:
[path stroke];//描边写法
[path fill];//填充写法
2. 绘制曲线
-
效果图:
我们要绘制的效果:
主要使用的这个方法:
//controlPoint是拐弯的点
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;
绘制直线时候记录的两种方法,我们为了再记忆一下绘制顺序,继续使用第一种:
- (void)drawRect:(CGRect)rect {
//1.获取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
//2.描述路径
UIBezierPath * path = [UIBezierPath bezierPath];
//设置起点
[path moveToPoint:CGPointMake(50, 100)];
//添加一根曲线到某个点 controlPoint是拐弯的点
[path addQuadCurveToPoint:CGPointMake(150, 100) controlPoint:CGPointMake(100, 50)];
//3.把路径添加到上下文
CGContextAddPath(ctx, path.CGPath);
//4.把上下问的内容渲染到view的layer
CGContextStrokePath(ctx);
}
3. 绘制矩形
-
效果图
我们要绘制的效果图:
主要使用方法
//rect:矩形的frame x,y,宽、高
+ (instancetype)bezierPathWithRect:(CGRect)rect;
- (void)drawRect:(CGRect)rect {
//1.获取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
//2.描述路径
UIBezierPath * path = [UIBezierPath bezierPathWithRect:CGRectMake(100, 100, 100, 50)];
//3.把路径添加到上下文
CGContextAddPath(ctx, path.CGPath);
//4.把上下问的内容渲染到view的layer
CGContextStrokePath(ctx);
}
上面我们使用的是描边显示,我们现在试一下填充方法(fill):
//把上面的渲染方法换为填充
CGContextFillPath(ctx);
4.圆
-
效果图
- 主要使用方法
//描述圆角矩形 cornerRadius:圆角半径
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius; // rounds all corners with the same horizontal and vertical radius
- (void)drawRect:(CGRect)rect {
//绘制曲线
UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(100, 100, 100, 100) cornerRadius:50];
//[path fill];
//绘制到view的layer上
[path stroke];
}
5. 椭圆
-
效果图
主要使用方法
rect :x,y,横直径、纵直径
+ (instancetype)bezierPathWithOvalInRect:(CGRect)rect;
- (void)drawRect:(CGRect)rect {
UIBezierPath * path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 100, 50, 100)];
[path stroke];
}
6 . 弧形
-
效果图
主要使用方法
//ArcCenter:所在圆的圆心
//radius:半径
//startAngle:开始角度;0度是在圆的最右侧;向上:度数是负的;向下:度数是正的
//endAngle:截止角度(到哪个位置)
//clockwise:是否为顺时针(怎么到达)
方法1:
+ (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
方法2:
- (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise NS_AVAILABLE_IOS(4_0);
//方法1:
- (void)drawRect:(CGRect)rect {
CGPoint center = CGPointMake(rect.size.width/2.0, rect.size.height/2.0);
CGFloat radius = rect.size.width/2.0 - 10;
//思路:起点圆的最右侧;终点为-M_PI_2的位置(即向上90度的位置);从最右侧顺时针到达向上90度的位置
UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0 endAngle:-M_PI_2 clockwise:YES];
[path stroke];
}
//方法2:
- (void)drawRect:(CGRect)rect {
CGPoint center = CGPointMake(rect.size.width/2.0, rect.size.height/2.0);
CGFloat radius = rect.size.width/2.0 - 10;
//这里调用方法不一样
UIBezierPath * path = [UIBezierPath bezierPath];
[path addArcWithCenter:center radius:radius startAngle:0 endAngle:-M_PI_2 clockwise:YES];
[path stroke];
}
7. 扇形
-
效果图
主要使用方法:
- (void)closePath;
思路:相当于是在弧上面添加两条直线到圆心,这样就形成了扇形
- (void)drawRect:(CGRect)rect {
//画出弧
CGPoint center = CGPointMake(rect.size.width/2.0, rect.size.height/2.0);
CGFloat radius = rect.size.width/2.0 - 10;
UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0 endAngle:-M_PI_2 clockwise:NO];
//从弧上添加一条线到圆心
[path addLineToPoint:center];
//效果1--描边,我们添加一条线到圆心之后,需要把另外一条也加上,使用closePath方法即可:
//关闭路径(从路径的终点连接一根到路径的起点)
[path closePath];
[path stroke];
//效果2--填充,我们添加一条线之后,直接调用填充方法,另外一条线默认加上了
// [path fill];
}
四.上下文状态
1.设置上下文状态
设置图形的上下文状态,可以改变绘图的颜色、宽度等。我们只是看其中一部分。
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
UIBezierPath * path = [UIBezierPath bezierPathWithRect:CGRectMake(100, 100, 50, 50)];
//设置线条宽度
CGContextSetLineWidth(ctx, 10);
//设置棱角
CGContextSetLineJoin(ctx, kCGLineJoinRound);
//设置颜色
[[UIColor yellowColor] set];
CGContextAddPath(ctx, path.CGPath);
CGContextStrokePath(ctx);
}
2.图形上下文状态栈
我们给上下文设置了状态,这种状态会运用到所有的路径当中。
例如,我们按照在添加两条直线,设置了一个状态,所有直线都会运用这个状态,就是图1的效果。
//制作两个不同颜色的路径
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
//第一条线
UIBezierPath * path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(20, 150)];
[path addLineToPoint:CGPointMake(280, 150)];
CGContextAddPath(ctx, path.CGPath);
CGContextSetLineWidth(ctx, 10);
[[UIColor redColor] set];
//第二条线
UIBezierPath * path2 = [UIBezierPath bezierPath];
[path2 moveToPoint:CGPointMake(150, 20)];
[path2 addLineToPoint:CGPointMake(150, 280)];
CGContextAddPath(ctx, path2.CGPath);
//取出上下文当中所有绘制的路径
//把上下文当中的状态应用到所有路径中
CGContextStrokePath(ctx);
}
现在我们想实现图2的效果。我们可以分开两次来绘制、渲染直线。也可以运用上下文栈,我们先用第一种方法:
同一个view上面设置不同的状态的路径--方法1:
- (void)drawRect:(CGRect)rect {
//制作两个不同颜色的路径
//----------------描绘第一个颜色的线--------------------
CGContextRef ctx = UIGraphicsGetCurrentContext();
UIBezierPath * path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(20, 150)];
[path addLineToPoint:CGPointMake(280, 150)];
CGContextAddPath(ctx, path.CGPath);
//设置第一条路径的状态
CGContextSetLineWidth(ctx, 10);
[[UIColor redColor] set];
//取出上下文当中所有绘制的路径
//把上下文当中的状态应用到所有路径中
CGContextStrokePath(ctx);
//----------------绘制另一个颜色的线--------------------
UIBezierPath * path2 = [UIBezierPath bezierPath];
[path2 moveToPoint:CGPointMake(150, 20)];
[path2 addLineToPoint:CGPointMake(150, 280)];
CGContextAddPath(ctx, path2.CGPath);
//设置第二条路径的状态
CGContextSetLineWidth(ctx, 10);
[[UIColor yellowColor] set];
CGContextStrokePath(ctx);
}
同一个view上面设置不同的状态的路径--方法2(状态栈):
上下文里面,一部分用来保存路径,一部分保存状态;
还有一个上下文状态栈(先进后出),我们可以把状态保存到里面,需要的时候把状态取出来直接使用;
//保存当前的状态栈
CGContextSaveGState(ctx);
//从当前上下文状态栈中取出顶栈的状态,覆盖当前的上下文状态(取出来之后就不存放在栈里面了)
CGContextRestoreGState(ctx);
- (void)drawRect:(CGRect)rect {
//----------------描绘第一个颜色的线--------------------
CGContextRef ctx = UIGraphicsGetCurrentContext();
UIBezierPath * path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(20, 150)];
[path addLineToPoint:CGPointMake(280, 150)];
CGContextAddPath(ctx, path.CGPath);
CGContextSetLineWidth(ctx, 20);
[[UIColor yellowColor] set];
//把当前黄色的上下文的状态保存到上下文状态栈(先进后出)
CGContextSaveGState(ctx);
CGContextSetLineWidth(ctx, 10);
[[UIColor redColor] set];
//取出上下文当中所有绘制的路径
//把上下文当中的状态应用到所有路径中
CGContextStrokePath(ctx);
//----------------绘制另一个颜色的线--------------------
UIBezierPath * path2 = [UIBezierPath bezierPath];
[path2 moveToPoint:CGPointMake(150, 20)];
[path2 addLineToPoint:CGPointMake(150, 280)];
CGContextAddPath(ctx, path2.CGPath);
//从上下文状态中取出状态
//从当前上下文状态栈中取出顶栈的状态,覆盖当前的上下文状态(取出来之后就不存放在栈里面了)
CGContextRestoreGState(ctx);
CGContextStrokePath(ctx);
}
3.图形上下文的矩阵操作
(void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
UIBezierPath * path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 100, 200, 100)];
[[UIColor redColor] set];
//形变操作 x变化多少,y变化多少
//平移
// CGContextTranslateCTM(ctx, 0, 100);
//缩放(放大的倍数)
CGContextScaleCTM(ctx, 1.5, 1.5);
//旋转
// CGContextRotateCTM(ctx, M_PI_4);
CGContextAddPath(ctx, path.CGPath);
CGContextFillPath(ctx);
}
上面的矩阵操作可以图形进行放大、旋转,顺便补充一下可以达到同样效果的transform知识点:
//相对于原点进行操作
CGAffineTransformMakeScale(<#CGFloat sx#>, <#CGFloat sy#>)
//相对于某个点进行操作
CGAffineTransformScale(CGAffineTransform t,CGFloat sx, CGFloat sy)
注意:
使用transform平移、旋转、缩放的话,避免两个同事使用,会错乱;
五.绘制文字
和上面所有操作一样,也是在view的draw里面进行;
我们给一个str进行绘制,不需要通过label就可以显示在view上面;
- (void)drawRect:(CGRect)rect {
NSString * drawStr = @"Quartz 2D";
[drawStr drawInRect:CGRectMake(10, 10, 200, 50) withAttributes:@{NSForegroundColorAttributeName:[UIColor purpleColor],
NSStrokeColorAttributeName:[UIColor redColor],//描边
NSFontAttributeName:[UIFont systemFontOfSize:25]}];
}
上面记录的是一些基本使用方法,后面会进行实例的记录。