Quartz2D

2016-09-18  本文已影响194人  PurpleWind

Quartz2D以及drawRect的重绘机制
字数1487 阅读21 评论1 喜欢1
一、什么是Quartz2D

Quartz2D是⼀个二维绘图引擎,同时支持iOS和Mac系统
Quartz2D的API是纯C语⾔言的Quartz2D的API来自于Core Graphics框架
Quartz2D的数据类型和函数基本都以CG作为前缀,例如下面2个类型:
1.CGContextRef
2.CGPathRef
二、Quartz 2D能完成的工作
绘制图形 : 线条\三角形\矩形\圆\弧,折线图,饼图,柱状图
绘制文字
绘制\生成图片(图像)
读取\生成PDF
屏幕截图\裁剪图片(例如截取游戏的五杀. 例如将矩形裁剪成圆形)
自定义UI控件
画板(可以在画板上画画)
手势解锁
图片加水印

三、Quartz2D在iOS开发中的价值

为了便于搭建美观的UI界面,iOS提供了UIKit框架,里面有各种各样的UI控件,
利用UIKit框架提供的控件,拼拼凑凑,能搭建和现实一些简单、常见的UI界面。
UILabel:显示文字
UIImageView:显示图片
UIButton:同时显示图片和文字(能点击)
......
但是,有些UI界面极其复杂、而且比较个性化,用普通的UI控件无法实现,这时可以利用Quartz2D技术将控件内部的结构画出来,类似自定义控件.其实,iOS中大部分控件的内容都是通过Quartz2D画出来的,因此,Quartz2D在iOS开发中很重要的一个价值是:自定义view(自定义UI控件)
四、图形上下文

图形上下文(Graphics Context)是一个CGContextRef类型的数据.
图形上下文的作用
保存绘图信息、绘图状态
相当于画布,不同类型的画布就是决定着画得内容将展示在哪里。
相同的一套绘图序列,指定不同的Graphics Context,就可将相同的图像绘制到不同的目标上,Quartz2D提供了以下几种类型的Graphics Context:
Bitmap Graphics Context 位图上下文,在这个上下文上绘制或者渲染的内容,可以获取成图片(需要主动创建一个位图上下文来使用,使用完毕,一定要销毁)
PDF Graphics Context
Window Graphics Context
Layer Graphics Context 图层上下文,针对UI控件的上下文
Printer Graphics Context

输出方式.png
五、利用Quartz2D绘制内容到自定义的view上

1.新建一个类,继承自UIView
2.实现- (void)drawRect:(CGRect)rect方法,然后在这个方法中,取得跟当前view相关联的图形上下文。
3.绘制相应的图形内容
4.利用图形上下文将绘制的所有内容渲染显示到view上面
六、核心方法drawRect:

为什么要实现drawRect:方法才能绘图到view上?
因为在drawRect:方法中才能取得跟view相关联的图形上下文
drawRect:方法在什么时候被调用?
当view第一次显示到屏幕上时(被加到UIWindow上显示出来)
调用view的setNeedsDisplay或者setNeedsDisplayInRect:时.
注意4点:
手动调用drawRect:方法,不会自动创建跟View相关联的上下文。应该
调用setNeedsDisplay方法,系统底层会自动调用drawRect,告诉系统重新绘制View.这样,系统底层会自动创建跟View相关联的上下文
setNeedsDisplay底层会调用drawRect,并不是立马调用的.只是设了一个调用的标志.调用时刻是等下一次屏幕刷新时才去调用drawRect。屏幕每一秒刷新30-60秒次,所以1秒调用drawRect方法大概30-60次,速度非常快哦
view内部有个layer(图层)属性,drawRect:方法中取得的是一个Layer Graphics Context,因此,绘制的东西其实是绘制到view的layer上去了
View之所以能显示东西,完全是因为它内部的layer
七、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)
九、常用绘制路径函数

一般以CGContextDraw、CGContextStroke、CGContextFill开头的函数,都是用来绘制路径的
Mode参数决定绘制的模式

void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode)
绘制空心路径

void CGContextStrokePath(CGContextRef c)
绘制实心路径

void CGContextFillPath(CGContextRef c)
十、矩阵操作

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

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

void CGContextRotateCTM(CGContextRef c, CGFloat angle)
平移

void CGContextTranslateCTM(CGContextRef c, CGFloat tx, CGFloat ty)
十一、案例

特别注意:
M_PI的含义:π
M_PI * 2的含义:2π

M_PI_2的含义:π/2
M_PI / 2的含义:π/2

// 画的图形路径
//bezierPathWithArcCenter:弧所在的圆心
//radius:圆的半径
//startAngle:开始角度,圆的最右侧为0度
//endAngle:截至角度,向下为正,向上为负.
//clockwise:时针的方向,yes:顺时针 no:逆时针
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:self.center radius:radius startAngle:startA endAngle:endA clockwise:NO];

画矩形、正方形

}

描边矩形.png

填充矩形.png
画扇形

}

描边扇形.png

填充扇形.png
画圆形

}

描边圆形.png

填充圆形.png
画圆角矩形

}

描边圆角矩形.png

填充圆角矩形.png
画直线

//设置线宽
CGContextSetLineWidth(ctx, 20);
//设置线的连接样式
CGContextSetLineJoin(ctx, kCGLineJoinBevel);
//设置线的顶角样式
CGContextSetLineCap(ctx, kCGLineCapRound);// 圆角线条
//设置线条颜色
[[UIColor redColor] set];

//3.把路径添加到上下文
CGContextAddPath(ctx, path.CGPath);
//4.把上下文当中绘制的内容渲染到跟View关联的layer
CGContextStrokePath(ctx);

}

线条.png

相交线条.png
画不规则图形+添加点击事件

CustomView.h文件

import <UIKit/UIKit.h>

@interface CustomView : UIView
@property (nonatomic ,strong) UIBezierPath *bezierPath;
@property (nonatomic, strong) UIColor *fillColor;

@end
CustomView.m文件

import "CustomView.h"

@implementation CustomView

-(void)drawRect:(CGRect)rect{
_bezierPath = [UIBezierPath bezierPath];
[_bezierPath moveToPoint:CGPointMake(320, 70)];
[_bezierPath addLineToPoint: CGPointMake(30, 130)];
[_bezierPath addLineToPoint: CGPointMake(80, 400)];
[_bezierPath addLineToPoint: CGPointMake(370, 570)];
[_bezierPath closePath];

//设置填充色(fillColor属性存储着外界赋给它的黄色)
[_fillColor setFill];
// 填充路径
[_bezierPath fill];

//设置描边色
[UIColor.redColor setStroke];
 _bezierPath.lineWidth = 4;
// 描边路径
[_bezierPath stroke];

}
@end
ViewController.m文件

import "ViewController.h"

import "CustomView.h"

@interface ViewController ()
@property (nonatomic, strong) CustomView *customView;
@end

@implementation ViewController

}

@end

不规则图形+点击事件.gif
画曲线

本塞尔曲线原理

贝塞尔曲线.gif

}

曲线.png
画饼图

做法1:

}

描边饼图.png

填充饼图.png

// [path stroke];
}
}

//随机生成一个颜色

}

重绘.gif
做法2:

//随机生成一个颜色

}

饼图-做法2.png
画文字

}

文字.png
模拟系统UIImageView是如何画出图片的
底层调用了 [self setNeedsDisplay];进行重绘,接着调用drawRect:画出图片

ViewController.m文件

import "ViewController.h"

import "ZBImageView.h"

@interface ViewController ()
@end

@implementation ViewController

import <UIKit/UIKit.h>

@interface ZBImageView : UIView

@property (nonatomic ,strong) UIImage *image;

@end
ZBImageView.m文件

import "ZBImageView.h"

@implementation ZBImageView

@end

import "VCView.h"

@implementation VCView

//setNeedsDisplay底层会调用drawRect,并不是立马调用的.只是设了一个调用的标志.调用时刻是等下一次屏幕刷新时才去调用drawRect

}

static int _snowY = 0;

}

}

@end

画图片.png
矩阵之缩放、移动、旋转UIView

CGContextAddPath(ctx, path.CGPath);
CGContextFillPath(ctx);

}

原始.png

缩放.png

移动.png

旋转.png
雪花+CA定时器+drawRect

//setNeedsDisplay底层会调用drawRect,并不是立马调用的.只是设了一个调用的标志.调用时刻是等下一次屏幕刷新时才去调用drawRect

}

static int _snowY = 0;

}

}

雪花.gif
画水印

import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageV;

@end

@implementation ViewController

NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[NSFontAttributeName] = [UIFont systemFontOfSize:20];
dict[NSForegroundColorAttributeName] = [UIColor redColor];

[str drawAtPoint:CGPointZero withAttributes:dict];
//4.从上下文当中生成一张图片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
//5.关闭位图上下文
UIGraphicsEndImageContext();


self.imageV.image = newImage;

}
@end

水印.png
裁剪图片

import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageV;

@end

@implementation ViewController

裁剪图片.png
屏幕截图

import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

//生成图片
//1.开启一个位图上下文
UIGraphicsBeginImageContext(self.view.bounds.size);
//2.把View的内容绘制到上下文当中
CGContextRef ctx =  UIGraphicsGetCurrentContext();
//UIView内容想要绘制到上下文当中, 必须使用渲染的方式
[self.view.layer renderInContext:ctx];
//3.从上下文当中生成一张图片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
//4.关闭上下文
UIGraphicsEndImageContext();
//把图片转成二进制流
//NSData *data = UIImageJPEGRepresentation(newImage, 1);
NSData *data = UIImagePNGRepresentation(newImage);

[data writeToFile:@"/Users/zhangbin/Desktop/CoderZbCoderZbCoderZb.jpg" atomically:YES];

}

@end

101.125.gif
配合手势裁剪图片

import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageV;
@property (nonatomic, assign)CGPoint startP;

@property (nonatomic, weak) UIView *coverView;

@end

@implementation ViewController

-(UIView *)coverView {

if (_coverView == nil) {
    //创建UIView
    UIView *coverView = [[UIView alloc] init];
    coverView.backgroundColor = [UIColor blackColor];
    coverView.alpha = 0.7;
    _coverView = coverView;
    [self.view addSubview:coverView];
}
return _coverView;

}

    //生成一张图片
    UIGraphicsBeginImageContext(self.imageV.bounds.size);

    //设置裁剪区域
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.coverView.frame];
    [path addClip];

    //2.把UIImageV当中的内容渲染到上下文当中
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    [self.imageV.layer renderInContext:ctx];

    //从上下文当中获取图片
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();

    //关闭上下文
    UIGraphicsEndImageContext();

    self.imageV.image = newImage;

    [self.coverView removeFromSuperview];
}

}
@end

101.126.gif
配合手势擦除图片

import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageV;

@end

@implementation ViewController

}

//开启一个位图上下文
//UIGraphicsBeginImageContext(self.imageV.bounds.size);
UIGraphicsBeginImageContextWithOptions(self.imageV.bounds.size, NO, 0);



CGContextRef ctx = UIGraphicsGetCurrentContext();
//把UIImageV的内容渲染到上下文当中
[self.imageV.layer renderInContext:ctx];

//擦除上下文当中指定的区域(即正方形区域(x, y, rectWH, rectWH) )
CGContextClearRect(ctx, rect);

UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
self.imageV.image = newImage;

//关闭上下文
UIGraphicsEndImageContext();

}

@end

101.127.gif
配合UITouch实现手势解锁链接密码

部分代码:

import "ClockView.h"

import "SVProgressHUD/SVProgressHUD.h"

define kDefaultBackNumber @"01258"

@interface ClockView()

@property (nonatomic ,strong) NSMutableArray *selectBtnArray;

@property (nonatomic, assign) CGPoint curP;

@end

@implementation ClockView

//添加子控件

//按功能模块抽方法
//获取当前手指的点

//给定一个点,判断这个点在不在按钮身上
//如果没有找到符合的条件,直接返回nil.

//手指开始点击

}

//手指移动

//手指离开

NSMutableString *str = [NSMutableString string];

 for (UIButton *btn in self.selectBtnArray) {
 btn.selected = NO;
 [str appendFormat:@"%ld",btn.tag];

 }

 NSLog(@"%@",str);

// 无论是画路径还是清空路径,必须得调用setNeedsDisplay

//清空路径
[self.selectBtnArray removeAllObjects];

//清空路径还必须调用重绘方法
[self setNeedsDisplay];

 if([str isEqualToString:kDefaultBackNumber]){
     NSLog(@"跳转");
     // 发出通知
     [[NSNotificationCenter defaultCenter] postNotificationName:@"CoderZb" object:nil userInfo:nil];
 }else{
     [SVProgressHUD showErrorWithStatus:@"手势错误"];
 }

}

}

}
@end
关键函数
CGRectContainsPoint(btn.frame, point)// 判断点point是否在btn中

101.138.gif
画板

iOS9 出现的Stack View控件,实现放置在Stack View内部的子控件的自动布局,相比于手动对每个控件设置约束,Stack View更加效率和精准。
ViewController.m文件

import "ViewController.h"

import "DrawView.h"

@interface ViewController ()<UINavigationControllerDelegate,UIImagePickerControllerDelegate>

@property (weak, nonatomic) IBOutlet DrawView *drawView;

@end

@implementation ViewController

}
//属于谁的事, 谁来做
//清屏

//橡皮擦

}

//#pa - mark UIImagePickerControllerDelegate

//保存

}

//当写入完成时调用

}
//设置线宽度

}
//设置线的颜色

@end
DrawView.h文件

import <UIKit/UIKit.h>

@interface DrawView : UIView

//清屏

@property (nonatomic ,strong) UIImage *image;

@end
DrawView.m文件

import "DrawView.h"

import "MyBezierPath.h"

@interface DrawView()

/** <#注释#>*/
@property (nonatomic ,strong) UIBezierPath *path;

/** <#注释#>*/
@property (nonatomic ,strong) NSMutableArray *pathArray;

@property (nonatomic , assign) CGFloat width;

/** <#注释#>*/
@property (nonatomic ,strong) UIColor *color;

@end

@implementation DrawView

//清屏

//设置线宽度

}
// 绘制路径

}

@end
MyBezierPath.h文件

import <UIKit/UIKit.h>

@interface MyBezierPath : UIBezierPath
@property (nonatomic ,strong) UIColor *color;

@end

上一篇下一篇

猜你喜欢

热点阅读