一、绘制基本图形
1、Quartz 2D是一个二维绘图引擎,同时支持iOS和Mac系统(Quartz 2D是封装好一套绘图的库,里面放有很多绘图框架提供使用)
Quartz 2D在ios开发中很重要的价值就是自定义view(自定义UI控件)
Quartz 2D能完成的工作:
1>绘制图形 : 线条\三角形\矩形\圆\弧等
2>绘制文字
3>绘制\生成图片(图像)
4>读取\生成PDF
5>截图\裁剪图片
6>自定义UI控件(最大的作用自定义view)
为了便于搭建美观的UI界面,iOS提供了UIKit框架,里面有各种各样的UI控件
UILabel:显示文字
UIImageView:显示图片
UIButton:同时显示图片和文字(能点击)
… …
利用UIKit框架提供的控件,拼拼凑凑,能搭建和现实一些简单、常见的UI界面
但是,有些UI界面极其复杂、而且比较个性化,用普通的UI控件无法实现,这时可以利用Quartz2D技术将控件内部的结构画出来,自定义控件的样子
其实,iOS中大部分控件的内容都是通过Quartz2D画出来的
因此,Quartz2D在iOS开发中很重要的一个价值是:自定义view(自定义UI控件)
Quartz 2D能做很多强大的事情,例如
裁剪图片
涂鸦\画板
手势解锁
2、图形上下文(Graphics Context):是一个CGContextRef类型的数据
图形上下文的作用
保存绘图信息、绘图状态
图形上下文类型不同决定绘制的输出目标(绘制到什么地方去?)
(输出目标可以是PDF文件、Bitmap或者显示器的窗口上)
相同的一套绘图序列,指定不同的Graphics Context,就可将相同的图像绘制到不同的目标上
Quartz2D提供了以下几种类型的Graphics Context:
Bitmap Graphics Context
PDF Graphics Context
Window Graphics Context
Layer Graphics Context
Printer Graphics Context
如何利用Quartz2D自定义view?(自定义UI控件)
如何利用Quartz2D绘制东西到view上?
首先,得有图形上下文,因为它能保存绘图信息,并且决定着绘制到什么地方去
其次,那个图形上下文必须跟view相关联,才能将内容绘制到view上面
以下实现的是Layer Graphics Context类型的:
自定义view的步骤:
1>新建一个类,继承自UIView
2>实现- (void)drawRect:(CGRect)rect方法(此方法中默认自动生成一个与view相关联的图形上下文)
3>在这个方法中取得跟当前view相关联的图形上下文
4>绘制相应的图形内容
5>利用图形上下文将绘制的所有内容渲染显示到view上面
为什么要实现drawRect:方法才能绘图到view上?
因为在drawRect:方法中才能取得跟view相关联的图形上下文
drawRect:方法在什么时候被调用?
当view第一次显示到屏幕上时调用(被加到UIWindow上显示出来)
调用view的setNeedsDisplay或者setNeedsDisplayInRect:时
Quartz2D的API是纯C语言的,Quartz2D的API来自于Core Graphics框架
数据类型和函数基本都以CG作为前缀
CGContextRef
CGPathRef
CGContextStrokePath(ctx);
在drawRect:方法中取得上下文后,就可以绘制东西到view上
具体实现步骤:
1>自定义View
2>在view的根类中实现- (void)drawRect:(CGRect)rect 方法
3>在方法中=>
- (void)drawRect:(CGRect)rect
{
(1)画线
//获取图形上下文(获取,创建上下文,都以UIGraphics开头)
CGContextRef context = UIGraphicsGetCurrentContext();
//绘制路径
UIBezierPath *path = [UIBezierPath bezierPath];
//设置起点
[path moveToPoint:CGPointMake(50, 50)];
//添加一根线到终点
[path addLineToPoint:CGPointMake(100, 100)];
//设置线的颜色
[[UIColor redColor] set];
//设置线的宽度
CGContextSetLineWidth(context, 20);
//把绘制的内容添加到上下文当中
CGContextAddPath(context, path.CGPath);
//把上下文的内容显示到View上(渲染到View的layer)
CGContextStrokePath(context);
//CGContextFillPath(context);
(2)曲线
CGContextRef ctx = UIGraphicsGetCurrentContext();
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(20, 100)];
//controlPoint控制点
[path addQuadCurveToPoint:CGPointMake(100, 100) controlPoint:CGPointMake(50, 30)];
CGContextAddPath(ctx, path.CGPath);
CGContextStrokePath(ctx);
(3)画三角形
CGContextRef context = UIGraphicsGetCurrentContext();
//绘制路径
UIBezierPath *path = [UIBezierPath bezierPath];
//设置起点
[path moveToPoint:CGPointMake(50, 50)];
//添加一根线到终点,并且以终点作为起点再画一条线
[path addLineToPoint:CGPointMake(100, 100)];
[path addLineToPoint:CGPointMake(50, 200)];
//设置线的颜色
[[UIColor redColor] set];
//线的冒冒设置为圆角
CGContextSetLineCap(context, kCGLineCapRound);
//设置线的宽度
CGContextSetLineWidth(context, 5);
//把绘制的内容添加到上下文当中
CGContextAddPath(context, path.CGPath);
//终点与起点闭合
CGContextClosePath(context);
//把上下文的内容显示到View上(渲染到View的layer)
CGContextStrokePath(context);
//CGContextFillPath(context);(Fill有填充及自动关闭路径的功能)
(4)画矩形
1>//获取图形上下文(获取,创建上下文,都以UIGraphics开头)
CGContextRef context = UIGraphicsGetCurrentContext();
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(50, 50, 50, 100)];(宽高一样就是正方形)
2>只写下面方法能快速填充出一个矩形
[[UIColor redColor]set];
UIRectFill(CGRectMake(50, 50, 50, 50));
(5)圆角矩形(如果当前的矩形是正方形设置圆角半径等于宽画成圆了,如果不是正方形,设置圆角半径等于宽画成椭圆)
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 50, 100, 100) cornerRadius:20];
[[UIColor redColor]set];
CGContextAddPath(ctx, path.CGPath);
//CGContextStrokePath(ctx);
CGContextFillPath(ctx);(填充)
(6)画椭圆(宽高一样,画成圆)
//注意:以后使用以下的写法,可以省列很多步骤
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 100, 100, 50)];
[path stroke];//(该方法中封装了以下步骤:1、获取上下文 2、描述路径 3、把路径添加到上下文 4、把上下文的内容渲染到view上)
[path fill];(填充)//跟以上同有封装功能
(7)画弧
//参数Center:弧所在的圆心
//参数radius:圆的半径
//参数startAngle:开始的角度
//参数endAngle:截止角度
//参数clockwise:YES顺时针 NO:逆时针
//注意:不能直接调用父类的self.center,是因为self.center坐标是相对于它的父控件,不能使用,必须要使用自定义view的坐标,drawRect方法提供的参数rect就是自定义view的范围
CGPoint center =CGPointMake(rect.size.width *0.5, rect.size.height *0.5);
CGFloat radius =rect.size.width *0.5 - 10;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:M_PI_2 endAngle:-M_PI_2 clockwise:YES];
[path stroke];
(8)扇形
//参数Center:弧所在的圆心
//参数radius:圆的半径
//参数startAngle:开始的角度
//参数endAngle:截止角度
//参数clockwise:YES顺时针 NO:逆时针
CGPoint center =CGPointMake(rect.size.width *0.5, rect.size.height *0.5);
CGFloat radius =rect.size.width *0.5 - 10;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:- M_PI endAngle:-M_PI_2 clockwise:YES];
[path addLineToPoint:center];
[[UIColor redColor]set];
// [path closePath];
// [path stroke];
[path fill];
(9)画文字
NSString *str = @"我爱您sdgsdgsdgfdbfdsfagsdgsg";
NSMutableDictionary *dictM = [NSMutableDictionary dictionary];
dictM[NSFontAttributeName] = [UIFont systemFontOfSize:50 weight:20];
dictM[NSForegroundColorAttributeName] = [UIColor redColor];
//描边
dictM[NSStrokeWidthAttributeName] = @3;
dictM[NSStrokeColorAttributeName] = [UIColor blueColor];
//设置阴影
NSShadow *shadow = [[NSShadow alloc]init];
shadow.shadowColor = [UIColor orangeColor];
shadow.shadowOffset = CGSizeMake(1, 2);
dictM[NSShadowAttributeName] = shadow;
//[str drawAtPoint:CGPointMake(0, 0) withAttributes:dictM];
[str drawInRect:rect withAttributes:dictM];//此方法会自动换行
(10)画图片
UIImage *image = [UIImage imageNamed:@"people_wangxinling"];
// UIRectClip(CGRectMake(0, 0, 50, 50));(裁剪,必须在绘制之前进行设置)
// [image drawInRect:rect];(把药绘制的图片给填充给定的区域当中)
// [image drawAtPoint:CGPointMake(0, 0)];//(绘制的是原始图片的大小)
[image drawAsPatternInRect:rect];(平铺)
(11)饼图(在自定义view的跟类中实现)
- (void)drawRect:(CGRect)rect {
NSArray *arr = @[@25,@25,@50];
CGPoint center = CGPointMake(rect.size.width *0.5, rect.size.height *0.5);
CGFloat radius = rect.size.width *0.5 - 10;
CGFloat startA = 0;
CGFloat angle = 0;
CGFloat endA = 0;
for (NSNumber *num in arr) {
startA = endA;
angle = num.intValue/100.0 *M_PI *2;
endA = startA + angle;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius: radius startAngle:startA endAngle:endA clockwise:YES];
[[self radamColor]set];
[path addLineToPoint:center];
[path fill];
}
}
- (UIColor *)radamColor
{
CGFloat r = arc4random_uniform(256)/255.0;
CGFloat g = 0;
CGFloat b = 0;
while ( r == g) {
g = arc4random_uniform(256)/255.0;
}
while ( r == g == b) {
b = arc4random_uniform(256)/255.0;
}
return [UIColor colorWithRed:r green:g blue:b alpha:1.0];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self setNeedsDisplay];
}
3、下载进度条例子实现
//%在stringWithFormat有特殊含义,不能直接使用,如果想要使用用两个%代表一个%
self.LabelSlider.text = [NSString stringWithFormat:@"%.2f%%", sender.value];
(2)注意:- (void)drawRect:方法如果是手动调用的话,它是不会给你创建view相关联的上下文,只有系统调用该方法时,才会创建跟view相关联的上下文,所以一般使用以下方法,让系统自动调用- (void)drawRect:方法
[self setNeedsDisplay];
4、新的定时器(不使用NSTimer,在自定义view的跟类中实现)
- (void)awakeFromNib
{
[super awakeFromNib];
//旧的定时器
//[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(changeY) userInfo:nil repeats:YES];
//新的定时器
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(changeY)];
//想要让CADisplayLink让它工作,必须要把他添加到主运行循环当中
//当每一次屏幕刷新的时候就会调用指定的方法(屏幕每一秒刷新60次)
[link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
CGFloat cc = 0;
- (void)changeY
{
cc += 10;
if (cc > self.bounds.size.height) {
cc = 0;
}
//setNeedsDisplay会调用drawRect:方法,但是它并不是立马调用,只是设了一个表示,当下一次屏幕刷新的时候才会调用drawRect:方法,所以与上面新的定时器同步跑时看起来比较顺滑
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
UIImage *image = [UIImage imageNamed:@"people_wangxinling"];
[image drawInRect:CGRectMake(0, cc, 50, 50)];
}
5、图形上下文栈(面试题)
(1)//一执行下面的方法,就会获取跟view相关联的上下文,在内存中分配存储空间
//上下文在内存中的结构分为上下两个区域:1>上面区域是保存路径,2>下面区域是保存上下文的状态(默认此区域的线宽为1,颜色为黑色)
CGContextRef ctx = UIGraphicsGetCurrentContext();
(2)//描述路径
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(20, 20)];
[path addLineToPoint:CGPointMake(60, 60)];
//以下代码对上下文状态区域的状态进行修改
CGContextSetLineWidth(ctx, 10);
[UIColor redColor] set];
(3)//把路径添加到上下文中(当执行完这行代码,就把线添加到上下文保存路径的区域)
CGContextAddPath(ctx, path.CGPath);
//
(4)//把上下文中的内容渲染到view当中
当执行次操作:会到上下文内存的保存路径的区域取出全部内容,然后把保存在上下文状态区域里的状态设置给取出的内容
CGContextStrokePath(ctx);
CGContextFillPath(ctx);
补一://保存当前上下文的状态(当执行此代码,就把上下文状态区域里的状态复制一份保存到状态栈中)
CGContextSaveGState(ctx);
补二://从上下文状态栈当中恢复上下文的状态(执行此代码,取出上下文状态栈中最上面的状态来进行设值,并且恢复上下文状态区域的状态为上下文状态栈中最上面的状态)
CGContextRestoreGState(ctx);
6、图形上下文的矩阵操作
CGContextTranslateCTM(ctx, 10, 10);
//旋转(最常用)
CGContextRotateCTM(ctx, M_PI);
//缩放
CGContextScaleCTM(ctx, 1.5, 1.5);
7、图片加水印(是生成一张新的图片,在任何地方都可实现,不是在view上画东西,所以不需要在 - (void)drawRect:(CGRect)rect实现)
位图上下文需要手动去开启,开启多大的上下文,生成的图片就多大
//0、加载图片
UIImage *image = [UIImage imageNamed:@"people_caoying"];
//1、开启一个跟图片原始大小的上下文
//参数:opaque不透明
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
//2.把图片绘制到上下文当中
[image drawAtPoint:CGPointZero];
//3.把文字绘制到上下文当中
NSString *str = @"x阿贝";
NSMutableDictionary *dictM = [NSMutableDictionary dictionary];
dictM[NSFontAttributeName] = [UIFont systemFontOfSize:100 weight:20];
dictM[NSForegroundColorAttributeName] = [UIColor redColor];
[str drawAtPoint:CGPointMake(10,20) withAttributes:dictM];
//4.从上下文当中生成一张新的图片
UIImage *newimage = UIGraphicsGetImageFromCurrentImageContext();
//5.关闭上下文
UIGraphicsEndPDFContext();
self.imageV.image = newimage;
Mode参数决定绘制的模式
void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode)
绘制空心路径
void CGContextStrokePath(CGContextRef c)
绘制实心路径
void CGContextFillPath(CGContextRef c)
提示:一般以CGContextDraw、CGContextStroke、CGContextFill开头的函数,都是用来绘制路径的
将当前的上下文copy一份,保存到栈顶(那个栈叫做”图形上下文栈”)
void CGContextSaveGState(CGContextRef c)
将栈顶的上下文出栈,替换掉当前的上下文
void CGContextRestoreGState(CGContextRef c)