首页 > 代码库 > 008-Quartz2D

008-Quartz2D

 掌握

  •  1.利用drawRect:方法绘图三部曲
 
  •  2.常见图形的绘制:线条、多边形、圆
 
  •  3.绘图状态的设置:文字颜色、线宽等
 
  •  4.图形上下文状态的保存与恢复(图形上下文栈)
 
  •  5.自定义View
 
  •  6.图片裁剪、截图

•  1.基本概念

问题一:什么是Quartz2D?

  •Quartz 2D是一个二维绘图引擎,同时支持iOS和Mac系统
  •Quartz2D的API是纯C语言的
  •Quartz2D的API来自于Core Graphics框架
  •数据类型和函数基本都以CG作为前缀  ØCGContextRef  ØCGPathRef  ØCGContextStrokePath(ctx);  Ø……
  •Quartz 2D能完成的工作
  Ø绘制图形 : 线条\三角形\矩形\圆\弧等
  Ø绘制文字\涂鸦\画板\手势解锁
  Ø绘制\生成图片(图像)
  Ø读取\生成PDF
  Ø截图\裁剪图片
  Ø报表:折线图\饼状图\柱状图
  Ø… …
问题二:为什么要使用Quartz2D?
  为了便于搭建美观的UI界面,iOS提供了UIKit框架,里面有各种各样的UI控件  
  ØUILabel:显示文字
  ØUIImageView:显示图片
  ØUIButton:同时显示图片和文字(能点击)
  Ø… …
  •利用UIKit框架提供的控件,拼拼凑凑,能搭建和现实一些简单、常见的UI界面
  •但是,有些UI界面极其复杂、而且比较个性化,用普通的UI控件无法实现,这时可以利用Quartz2D技术将控件内部的结构画出来,自定义控件的样子
  •其实,iOS中大部分控件的内容都是通过Quartz2D画出来的
  •因此,Quartz2D在iOS开发中很重要的一个价值是:自定义view(自定义UI控件)

问题三:什么是图形上下文?

•图形上下文(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


•  2.基本绘图的实现

问题一:为什么要实现drawRect:方法才能绘图到view上,该方法在什么时候被调用?

1》.因为在drawRect:方法中才能取得跟view相关联的图形上下文
2》.Ø当view第一次显示到屏幕上时(被加载到UIWindow上显示出来时)
  Ø调用view的setNeedsDisplay或者setNeedsDisplayInRect:时 

问题二:如何利用Quartz2D自定义view?(自定义UI控件)

•利用Quartz2D绘制东西到view上的先决条件:
Ø首先,得有图形上下文,因为它能保存绘图信息,并且决定着绘制到什么地方去
Ø其次,那个图形上下文必须跟view相关联,才能将内容绘制到view上面
•自定义view的步骤
Ø新建一个类,继承自UIView
Ø实现- (void)drawRect:(CGRect)rect方法,然后在这个方法中
  1>.取得跟当前view相关联的图形上下文
   ps:  •在drawRect:方法中取得上下文后,就可以绘制东西到view上
      •View内部有个layer(图层)属性,drawRect:方法中取得的是一个Layer Graphics Context,因此,绘制的东西其实是绘制到view的layer上去了
      •View之所以能显示东西,完全是因为它内部的layer
  2>.绘制相应的图形内容
  3>.利用图形上下文将绘制的所有内容渲染显示到view上面

问题三:如何利用代码实现Quartz2D绘图三部曲?

1.获得图形上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
2.拼接路径(下面代码是搞一条线段)
CGContextMoveToPoint(ctx, 10, 10);  // 起始点CGContextAddLineToPoint(ctx, 100, 100); // 添加一根线段直到这个点
3.绘制路径
CGContextStrokePath(ctx); // CGContextFillPath(ctx); 

问题四:绘图顺序? 

问题五:常见图形拼接路径函数

•新建一个起点

void CGContextMoveToPoint(CGContextRef c, CGFloat x, CGFloat y)

•添加新的线段到某个点

void CGContextAddLineToPoint(CGContextRef c, CGFloat x, CGFloat y)

•添加一个矩形

void CGContextAddRect(CGContextRef c, CGRect rect)

•添加一个椭圆

void CGContextAddEllipseInRect(CGContextRef context, CGRect rect)

•添加一个圆弧

void CGContextAddArc(CGContextRef c, CGFloat x, CGFloat y,

  CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise)

•Mode参数决定绘制的模式

void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode)

•绘制空心路径

void CGContextStrokePath(CGContextRef c)

•绘制实心路径

void CGContextFillPath(CGContextRef c)

提示:一般以CGContextDraw、CGContextStroke、CGContextFill开头的函数,都是用来绘制路径的

问题六:常见的操作

1.图形上下文栈的操作

•将当前的上下文copy一份,保存到栈顶(那个栈叫做”图形上下文栈”)

void CGContextSaveGState(CGContextRef c)

•将栈顶的上下文出栈,替换掉当前的上下文

void CGContextRestoreGState(CGContextRef c)

2.矩阵操作

•利用矩阵操作,能让绘制到上下文中的所有路径一起发生变化
Ø缩放

void CGContextScaleCTM(CGContextRef c, CGFloat sx, CGFloat sy) 

Ø旋转

void CGContextRotateCTM(CGContextRef c, CGFloat angle)

Ø平移

void CGContextTranslateCTM(CGContextRef c, CGFloat tx, CGFloat ty)

问题七:实用功能

1.图片水印

•水印:在图片上加的防止他人盗图的半透明logo、文字、图标
•水印的作用
Ø告诉你这个图片从哪来的
Ø主要是一些网站为了版权问题、广告而添加的
•有时候,在手机客户端app中也需要用到水印技术
•比如,用户拍完照片后,可以在照片上打个水印,标识这个图片是属于哪个用户的
•实现方式:利用Quartz2D,将水印(文字、LOGO)画到图片的右下角
•核心代码
Ø开启一个基于位图的图形上下文
void    UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale)
Ø从上下文中取得图片(UIImage)
UIImage* UIGraphicsGetImageFromCurrentImageContext();
Ø结束基于位图的图形上下文
void     UIGraphicsEndImageContext();
•图片水印实例
#import "UIImage+WHB.h"@implementation UIImage (WHB)+ (instancetype)waterImageWithBg:(NSString *)bg logo:(NSString *)logo{    UIImage *bgImage = [UIImage imageNamed:bg];        // 1.创建一个基于位图的上下文(开启一个基于位图的上下文)    UIGraphicsBeginImageContextWithOptions(bgImage.size, NO, 0.0);        // 2.画背景    [bgImage drawInRect:CGRectMake(0, 0, bgImage.size.width, bgImage.size.height)];        // 3.画右下角的水印    UIImage *waterImage = [UIImage imageNamed:logo];    CGFloat scale = 0.2;    CGFloat margin = 5;    CGFloat waterW = waterImage.size.width * scale;    CGFloat waterH = waterImage.size.height * scale;    CGFloat waterX = bgImage.size.width - waterW - margin;    CGFloat waterY = bgImage.size.height - waterH - margin;    [waterImage drawInRect:CGRectMake(waterX, waterY, waterW, waterH)];        // 4.从上下文中取得制作完毕的UIImage对象    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();        // 5.结束上下文    UIGraphicsEndImageContext();        return newImage;}@end
UIImage+WHB.m
 得到的水印可以写入沙盒文件里面去
    // 将image对象压缩为PNG格式的二进制数据    NSData *data =http://www.mamicode.com/ UIImagePNGRepresentation(newImage);    //    UIImageJPEGRepresentation(<#UIImage *image#>, <#CGFloat compressionQuality#>)     // 写入文件    NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"new.png"];    [data writeToFile:path atomically:YES];

 2.图片裁剪 

•很多app的头像,都是圆形的
•这时需要把一张普通的图片刻意裁剪成圆形
•核心代码
void CGContextClip(CGContextRef c)

将当前上下所绘制的路径裁剪出来(超出这个裁剪区域的都不能显示)

•图片裁剪代码实例:

#import "UIImage+WHB.h"@implementation UIImage (WHB)+ (instancetype)circleImageWithName:(NSString *)name borderWidth:(CGFloat)borderWidth borderColor:(UIColor *)borderColor{    // 1.加载原图    UIImage *oldImage = [UIImage imageNamed:name];        // 2.开启上下文    CGFloat imageW = oldImage.size.width + 2 * borderWidth;    CGFloat imageH = oldImage.size.height + 2 * borderWidth;    CGSize imageSize = CGSizeMake(imageW, imageH);    UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0.0);        // 3.取得当前的上下文    CGContextRef ctx = UIGraphicsGetCurrentContext();        // 4.画边框(大圆)    [borderColor set];    CGFloat bigRadius = imageW * 0.5; // 大圆半径    CGFloat centerX = bigRadius; // 圆心    CGFloat centerY = bigRadius;    CGContextAddArc(ctx, centerX, centerY, bigRadius, 0, M_PI * 2, 0);    CGContextFillPath(ctx); // 画圆        // 5.小圆    CGFloat smallRadius = bigRadius - borderWidth;    CGContextAddArc(ctx, centerX, centerY, smallRadius, 0, M_PI * 2, 0);    // 裁剪(后面画的东西才会受裁剪的影响)    CGContextClip(ctx);        // 6.画图    [oldImage drawInRect:CGRectMake(borderWidth, borderWidth, oldImage.size.width, oldImage.size.height)];        // 7.取图    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();        // 8.结束上下文    UIGraphicsEndImageContext();        return newImage;}@end
UIImage+WHB.m

 3.屏幕截图

•有时候需要截取屏幕上的某一块内容进行分享等

核心代码

- (void)renderInContext:(CGContextRef)ctx;

调用某个view的layer的renderInContext:方法即可

•屏幕截图代码实例:

@implementation UIImage (WHB)+ (instancetype)captureWithView:(UIView *)view{    // 1.开启上下文    UIGraphicsBeginImageContextWithOptions(view.frame.size, NO, 0.0);        // 2.将控制器view的layer渲染到上下文    [view.layer renderInContext:UIGraphicsGetCurrentContext()];        // 3.取出图片    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();        // 4.结束上下文    UIGraphicsEndImageContext();        return newImage;}@end
UIImage+WHB.m
- (IBAction)clip {    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{        // 1.捕捉        UIImage *newImage = [UIImage captureWithView:self.view];                // 2.写文件        NSData *data =http://www.mamicode.com/ UIImagePNGRepresentation(newImage);        NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"new.png"];        [data writeToFile:path atomically:YES];    });}
点击截图截下图片并写入到本地

 

ps:条纹背景

#import "WHBViewController.h"@interface WHBViewController ()@property (weak, nonatomic) IBOutlet UITextView *textView;@end@implementation WHBViewController- (void)viewDidLoad{    [super viewDidLoad];        // 1.创建一行背景图片    CGFloat rowW = self.view.frame.size.width;//    CGFloat rowH = 40;    CGFloat rowH = 30;    UIGraphicsBeginImageContextWithOptions(CGSizeMake(rowW, rowH), NO, 0.0);        CGContextRef ctx = UIGraphicsGetCurrentContext();    // 画矩形框    [[UIColor redColor] set];    CGContextAddRect(ctx, CGRectMake(0, 0, rowW, rowH));    CGContextFillPath(ctx);        // 2.画线    [[UIColor greenColor] set];    CGFloat lineWidth = 2;    CGContextSetLineWidth(ctx, lineWidth);    CGFloat dividerX = 0;    CGFloat dividerY = rowH - lineWidth;    CGContextMoveToPoint(ctx, dividerX, dividerY);    CGContextAddLineToPoint(ctx, rowW - dividerX, dividerY);    CGContextStrokePath(ctx);        // 3.取图    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();        // 4.结束上下文    UIGraphicsEndImageContext();        // 5.设置为背景    self.textView.backgroundColor = [UIColor colorWithPatternImage:newImage];}- (void)imageBg{    UIImage *oldImage = [UIImage imageNamed:@"me"];        UIGraphicsBeginImageContextWithOptions(self.view.frame.size, NO, 0.0);    [oldImage drawInRect:self.view.bounds];    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();    UIGraphicsEndImageContext();        self.view.backgroundColor = [UIColor colorWithPatternImage:newImage];}@end
WHBViewController.m

•  3.代码示例

画线条:                                                     图形效果:

- (void)drawRect:(CGRect)rect{    // Drawing code    // 1.获得图形上下文    CGContextRef ctx = UIGraphicsGetCurrentContext();        // 2.拼接图形(路径)    // 设置线段宽度    CGContextSetLineWidth(ctx, 10);        // 设置线段头尾部的样式    CGContextSetLineCap(ctx, kCGLineCapRound);        // 设置线段转折点的样式    CGContextSetLineJoin(ctx, kCGLineJoinRound);        /**  第1根线段  **/    // 设置颜色    CGContextSetRGBStrokeColor(ctx, 1, 0, 0, 1);    // 设置一个起点    CGContextMoveToPoint(ctx, 10, 10);    // 添加一条线段到(100, 100)    CGContextAddLineToPoint(ctx, 100, 100);        // 渲染一次    CGContextStrokePath(ctx);        /**  第2根线段  **/    // 设置颜色    CGContextSetRGBStrokeColor(ctx, 0, 0, 1, 1);    // 设置一个起点    CGContextMoveToPoint(ctx, 200, 190);    // 添加一条线段到(150, 40)    CGContextAddLineToPoint(ctx, 150, 40);    CGContextAddLineToPoint(ctx, 120, 60);          // 3.渲染显示到view上面    CGContextStrokePath(ctx);}
画线条

 画三角形

/** *  画三角形 */void drawTriangle(){    // 1.获得上下文    CGContextRef ctx = UIGraphicsGetCurrentContext();        // 2.画三角形    CGContextMoveToPoint(ctx, 0, 0);    CGContextAddLineToPoint(ctx, 100, 100);    CGContextAddLineToPoint(ctx, 150, 80);    // 关闭路径(连接起点和最后一个点)    CGContextClosePath(ctx);       CGContextSetRGBStrokeColor(ctx, 0, 1, 0, 1);        // 3.绘制图形    CGContextStrokePath(ctx);}
画三角形

 画矩形:

/** *  画四边形 */void draw4Rect(){    // 1.获得上下文    CGContextRef ctx = UIGraphicsGetCurrentContext();        // 2.画矩形    CGContextAddRect(ctx, CGRectMake(10, 10, 150, 100));        // set : 同时设置为实心和空心颜色    // setStroke : 设置空心颜色    // setFill : 设置实心颜色    [[UIColor whiteColor] set];    //    CGContextSetRGBFillColor(ctx, 0, 0, 1, 1);        // 3.绘制图形    CGContextFillPath(ctx);}
画矩形

画圆:

/** *  画圆 */void drawCircle(){    // 1.获得上下文    CGContextRef ctx = UIGraphicsGetCurrentContext();        // 2.画圆    CGContextAddEllipseInRect(ctx, CGRectMake(50, 10, 100, 100));        CGContextSetLineWidth(ctx, 10);        // 3.显示所绘制的东西    CGContextStrokePath(ctx);}
画圆
/** *  在view第一次显示到屏幕上的时候会调用一次 */- (void)drawRect:(CGRect)rect{    // 1.获得上下文    CGContextRef ctx = UIGraphicsGetCurrentContext();        // 2.画1/4圆    CGContextMoveToPoint(ctx, 100, 100);    CGContextAddLineToPoint(ctx, 100, 150);    CGContextAddArc(ctx, 100, 100, 50, -M_PI_2, M_PI, 1);    CGContextClosePath(ctx);        [[UIColor redColor] set];        // 3.显示所绘制的东西    CGContextFillPath(ctx);}
1/4圆

 画圆弧:

画圆弧

自定义ImageView:

- (void)drawRect:(CGRect)rect{    drawImage();}void drawImage(){    // 1.取得图片    UIImage *image = [UIImage imageNamed:@"me"];        // 2.画//    [image drawAtPoint:CGPointMake(50, 50)];//    [image drawInRect:CGRectMake(0, 0, 150, 150)];    [image drawAsPatternInRect:CGRectMake(0, 0, 200, 200)];        // 3.画文字    NSString *str = @"为xxx所画";    [str drawInRect:CGRectMake(0, 180, 100, 30) withAttributes:nil];}/** *  画文字 */void drawText(){    // 1.获得上下文    CGContextRef ctx = UIGraphicsGetCurrentContext();    // 2.画矩形    CGRect cubeRect = CGRectMake(50, 50, 100, 100);    CGContextAddRect(ctx, cubeRect);    // 3.显示所绘制的东西    CGContextFillPath(ctx);        // 4.画文字    NSString *str = @"哈哈哈哈Good morning hello hi hi hi hi";    //    [str drawAtPoint:CGPointZero withAttributes:nil];        NSMutableDictionary *attrs = [NSMutableDictionary dictionary];    // NSForegroundColorAttributeName : 文字颜色    // NSFontAttributeName : 字体    attrs[NSForegroundColorAttributeName] = [UIColor redColor];    attrs[NSFontAttributeName] = [UIFont systemFontOfSize:50];    [str drawInRect:cubeRect withAttributes:attrs];}
取得图片画文字

 图形上下文栈:                                    图形效果:

- (void)drawRect:(CGRect)rect{    // 1.获得上下文    CGContextRef ctx = UIGraphicsGetCurrentContext();        // 将ctx拷贝一份放到栈中    CGContextSaveGState(ctx);        // 设置绘图状态    CGContextSetLineWidth(ctx, 10);    [[UIColor redColor] set];    CGContextSetLineCap(ctx, kCGLineCapRound);        // 第1根线    CGContextMoveToPoint(ctx, 50, 50);    CGContextAddLineToPoint(ctx, 120, 190);        CGContextStrokePath(ctx);        // 将栈顶的上下文出栈,替换当前的上下文    CGContextRestoreGState(ctx);            // 第2根线    CGContextMoveToPoint(ctx, 10, 70);    CGContextAddLineToPoint(ctx, 220, 290);        CGContextStrokePath(ctx);//    CGContextDrawPath(ctx, kCGPathStroke);}
图形上下文栈

 矩阵操作:

/** 渐变色 虚线 pattern blend ..... ..... 阴影 */- (void)drawRect:(CGRect)rect{    CGContextRef ctx = UIGraphicsGetCurrentContext();        CGContextSaveGState(ctx);        CGContextRotateCTM(ctx, M_PI_4 * 0.4);    CGContextScaleCTM(ctx, 0.5, 0.5);    CGContextTranslateCTM(ctx, 0, 150);        CGContextAddRect(ctx, CGRectMake(10, 10, 50, 50));        CGContextStrokePath(ctx);        CGContextRestoreGState(ctx);        CGContextAddEllipseInRect(ctx, CGRectMake(100, 100, 100, 100));    CGContextMoveToPoint(ctx, 100, 100);    CGContextAddLineToPoint(ctx, 200, 250);        // 矩阵操作//    CGContextScaleCTM(ctx, 0.5, 0.5);        CGContextStrokePath(ctx);}
矩阵操作

裁剪:

- (void)drawRect:(CGRect)rect{    CGContextRef ctx = UIGraphicsGetCurrentContext();        CGContextSaveGState(ctx);        // 0.画圆    CGContextAddEllipseInRect(ctx, CGRectMake(100, 100, 100, 100));    // 裁剪圆形区域    CGContextClip(ctx);    CGContextFillPath(ctx);        // 1.显示图片    UIImage *image = [UIImage imageNamed:@"me"];    [image drawAtPoint:CGPointMake(100, 100)];        CGContextRestoreGState(ctx);        CGContextAddRect(ctx, CGRectMake(0, 0, 100, 100));    CGContextFillPath(ctx);}
View Code

 

重绘:                                              图形效果:  

@interface MJViewController ()- (IBAction)sizeChange:(UISlider *)sender;@property (weak, nonatomic) IBOutlet View *circleView;@end@implementation MJViewController- (IBAction)sizeChange:(UISlider *)sender {    self.circleView.radius = sender.value;}@end
ViewController.m
#import <UIKit/UIKit.h>@interface MJView : UIView/** *  圆的半径 */@property (nonatomic, assign) float radius;@end
View.h
#import "View.h"@implementation View- (void)setRadius:(float)radius{    _radius = radius;    // 重绘显示到屏幕上    [self setNeedsDisplay];}/** *  默认只会在view第一次显示的时候调用(只能由系统自动调用, 不能手动调用) */- (void)drawRect:(CGRect)rect{    NSLog(@"drwract---%f", self.radius);    CGContextRef ctx = UIGraphicsGetCurrentContext();        CGContextAddArc(ctx, 125, 125, self.radius, 0, M_PI * 2, 0);        CGContextFillPath(ctx);}@end
View.m

 动画:

#import "SnowView.h"@interface SnowView()@property (nonatomic, assign) CGFloat snowY;@end@implementation SnowView- (void)awakeFromNib{    CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(setNeedsDisplay)];    [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];        //    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(setNeedsDisplay) userInfo:nil repeats:YES];}- (void)drawRect:(CGRect)rect{    self.snowY+=5;        if (self.snowY >= rect.size.height) {        self.snowY = -100;    }        UIImage *image = [UIImage imageNamed:@"snow.jpg"];    [image drawAtPoint:CGPointMake(0, self.snowY)];}
动画