Quartz 2D (1)

一、绘制基本图形

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)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容