一.Quartz2D简介
Quartz2D是二维的绘图引擎(经包装的函数库,方便开发者使用。也就是说苹果帮我们封装了一套绘图的函数库),用Quartz2D写的同一份代码,既可以运行在iphone上又可以运行在mac上,可以跨平台开发,开发中比较常用的是截屏/裁剪/自定义UI控件,Quartz2D在iOS开发中的价值就是自定义UI控件.
二.绘图简介
1.绘图的步骤:
1.获取上下文
CG:表示这个类在CoreGraphics框架里 Ref:引用,目前学的上下文都跟UIGraphics有关,想获取图形上下文,首先敲UIGraphics。
2.创建路径(描述路径)
一般开发中用贝塞尔路径,里面封装了很多东西,可以帮我画一些基本的线段,矩形,圆等等
4.渲染上下文
首先获取图形上下文,然后描述路径,把路径添加到上下文,渲染到视图,图形上下文相当于一个内存缓存区,在内存里面操作是最快的,比直接在界面操作快多了
3.当这个View要显示的时候才会调用drawRect绘制图形,viewDidLoad后才会调用,因为那时候还没显示视图
4.注意:drawRect不能手动调用,因为图形上下文我们自己创建不了,只能由系统帮我们创建,并且传递给我们.需要我们手动调用 [self setNeedsDisplay](重绘,系统会先创建与view相关联的上下文,然后再调用drawRect)
三.Quartz2D绘图演练
1.绘制直线
- 最原始的绘图方式
// 1.获取图形上下文
// 目前我们所用的上下文都是以UIGraphics
// CGContextRef Ref:引用 CG:目前使用到的类型和函数 一般都是CG开头 CoreGraphics
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 2.描述路径
// 创建路径
CGMutablePathRef path = CGPathCreateMutable();
// 设置起点
// path:给哪个路径设置起点
CGPathMoveToPoint(path, NULL, 50, 50);
// 添加一根线到某个点
CGPathAddLineToPoint(path, NULL, 200, 200);
// 3.把路径添加到上下文
CGContextAddPath(ctx, path);
// 4.渲染上下文
CGContextStrokePath(ctx);
绘直线第二种方式
// 获取上下文 CGContextRef ctx = UIGraphicsGetCurrentContext(); // 描述路径 // 设置起点 CGContextMoveToPoint(ctx, 50, 50); CGContextAddLineToPoint(ctx, 200, 200); // 渲染上下文 CGContextStrokePath(ctx);
绘直线第三种方式
// UIKit已经封装了一些绘图的功能 // 贝瑟尔路径 // 创建路径 UIBezierPath *path = [UIBezierPath bezierPath]; // 设置起点 [path moveToPoint:CGPointMake(50, 50)]; // 添加一根线到某个点 [path addLineToPoint:CGPointMake(200, 200)]; // 绘制路径 [path stroke];
2.绘制曲线
- 最原始的绘图方式
// 原生绘制方法
// 获取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 描述路径
// 设置起点
CGContextMoveToPoint(ctx, 50, 50);
// cpx:控制点的x
CGContextAddQuadCurveToPoint(ctx, 150, 20, 250, 50);
// 渲染上下文
CGContextStrokePath(ctx);
3.绘制图形
- 圆弧
// Center:圆心
// startAngle:弧度
// clockwise:YES:顺时针 NO:逆时针
// 扇形
CGPoint center = CGPointMake(125, 125);
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:100 startAngle:0 endAngle:M_PI_2 clockwise:YES];
// 添加一根线到圆心
[path addLineToPoint:center];
// 封闭路径,关闭路径:从路径的终点到起点
[path closePath];
[path stroke];
// 填充:必须是一个完整的封闭路径,默认就会自动关闭路径
// [path fill];
- 饼状图
//随机绘制饼状图
- (NSArray *)arrRandom
{
int totoal = 100;
NSMutableArray *arrM = [NSMutableArray array];
int temp = 0;
for (int i = 0; i < arc4random_uniform(10) + 1; i++) {
temp = arc4random_uniform(totoal) + 1;
// 100 1~100
// 随机出来的临时值等于总值,直接退出循环,因为已经把总数分配完毕,没必要在分配。
[arrM addObject:@(temp)];
// 解决方式:当随机出来的数等于总数直接退出循环。
if (temp == totoal) {
break;
}
totoal -= temp;
}
//如果余下的值不为0的情况
if (totoal) {
[arrM addObject:@(totoal)];
}
return arrM;
}
- (void)drawRect:(CGRect)rect {
NSArray *arr = [self arrRandom];
CGFloat radius = rect.size.width * 0.5;
CGPoint center = CGPointMake(radius, radius);
CGFloat startA = 0;
CGFloat angle = 0;
CGFloat endA = 0;
for (int i = 0; i < arr.count; i++) {
startA = endA;
angle = [arr[i] integerValue] / 100.0 * M_PI * 2;
endA = startA + angle;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
[path addLineToPoint:center];
[[self colorRandom] set];
[path fill];
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//重新绘制
[self setNeedsDisplay];
}
- (UIColor *)colorRandom
{
// 0 ~ 255 / 255
// OC:0 ~ 1
CGFloat r = arc4random_uniform(256) / 255.0;
CGFloat g = arc4random_uniform(256) / 255.0;
CGFloat b = arc4random_uniform(256) / 255.0;
return [UIColor colorWithRed:r green:g blue:b alpha:1];
}
- 柱状图
- (void)drawRect:(CGRect)rect {
NSArray *arr = [self arrRandom];
CGFloat x = 0;
CGFloat y = 0;
CGFloat w = 0;
CGFloat h = 0;
for (int i = 0 ; i < arr.count; i++) {
w = rect.size.width / (2*arr.count - 1);
x = 2 * w * i;
h = [arr[i] floatValue] / 100.0 *rect.size.height;
y = rect.size.height - h;
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(x, y, w, h)];
[[self colorRandom] set];
[path fill];
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self setNeedsDisplay];
}
- (NSArray *)arrRandom {
int total = 100;
NSMutableArray *arrM = [NSMutableArray array];
int temp = 0;
for (int i = 0 ; i < arc4random_uniform(10) + 1; i++) {
temp = arc4random_uniform(total) + 1;
[arrM addObject:@(temp)];
if (temp == total) {
break;
}
total -= temp;
}
if (total) {
[arrM addObject:@(total)];
}
return arrM;
}
- (UIColor *)colorRandom
{
// 0 ~ 255 / 255
// OC:0 ~ 1
CGFloat r = arc4random_uniform(256) / 255.0;
CGFloat g = arc4random_uniform(256) / 255.0;
CGFloat b = arc4random_uniform(256) / 255.0;
return [UIColor colorWithRed:r green:g blue:b alpha:1];
}
- 绘制文字
// 绘制文字
NSString *str = @"asfdsfsdf";
// 文字的起点
// Attributes:文本属性
NSMutableDictionary *textDict = [NSMutableDictionary dictionary];
// 设置文字颜色
textDict[NSForegroundColorAttributeName] = [UIColor redColor];
// 设置文字字体
textDict[NSFontAttributeName] = [UIFont systemFontOfSize:30];
// 设置文字的空心颜色和宽度
textDict[NSStrokeWidthAttributeName] = @3;
textDict[NSStrokeColorAttributeName] = [UIColor yellowColor];
// 创建阴影对象
NSShadow *shadow = [[NSShadow alloc] init];
shadow.shadowColor = [UIColor greenColor];
shadow.shadowOffset = CGSizeMake(4, 4);
shadow.shadowBlurRadius = 3;
textDict[NSShadowAttributeName] = shadow;
// 富文本:给普通的文字添加颜色,字体大小
[str drawAtPoint:CGPointZero withAttributes:textDict]; //不会换行
- 绘制图片
// 超出裁剪区域的内容全部裁剪掉
// 注意:裁剪必须放在绘制之前
UIRectClip(CGRectMake(0, 0, 50, 50));//裁剪图片
UIImage *image = [UIImage imageNamed:@"001"];
// 默认绘制的内容尺寸跟图片尺寸一样大
[image drawAtPoint:CGPointZero];
//绘制图片在一定区域内
//[image drawInRect:rect];
// 绘图(平铺图片)
//[image drawAsPatternInRect:rect];
4.绘图中的定时器
如果在绘图的时候需要用到定时器,通常使用CADisplayLink
//CADisplayLink:每次屏幕刷新的时候就会调用,屏幕一般一秒刷新60次
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(timeChange)];
// 添加主运行循环
[link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
- (void)timeChange
{
//注意:这个方法并不会马上调用drawRect,其实这个方法只是给当前控件添加刷新的标记,等下一次屏幕刷新的时候才会调用drawRect
[self setNeedsDisplay];
}
5.矩阵操作(平移,缩放,旋转)
- (void)drawRect:(CGRect)rect {
// 1.获取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 2.描述路径
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(-100, -50, 200, 100)];
[[UIColor redColor] set];
// 上下文矩阵操作
// 注意:矩阵操作必须要在添加路径之前
// 平移
CGContextTranslateCTM(ctx, 100, 50);
// 缩放
CGContextScaleCTM(ctx, 0.5, 0.5);
// 旋转
CGContextRotateCTM(ctx, M_PI_4);
// 3.把路径添加上下文
CGContextAddPath(ctx, path.CGPath);
[[UIColor redColor] set];
// 4.渲染上下文
CGContextFillPath(ctx);
}
四.Quartz2D应用
1.图片水印
// 加载图片
UIImage *image = [UIImage imageNamed:@"小黄人"];
// 0.获取上下文,之前的上下文都是在view的drawRect方法中获取(跟View相关联的上下文layer上下文)
// 目前我们需要绘制图片到新的图片上,因此需要用到位图上下文
// 怎么获取位图上下文,注意位图上下文的获取方式跟layer上下文不一样。位图上下文需要我们手动创建。
// 开启一个位图上下文,注意位图上下文跟view无关联,所以不需要在drawRect.
// size:位图上下文的尺寸(新图片的尺寸)
// opaque: 不透明度 YES:不透明 NO:透明,通常我们一般都弄透明的上下文
// scale:通常不需要缩放上下文,取值为0,表示不缩放
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
UIBezierPath *path =[UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 300, 300)];
[[UIColor redColor] set];
[path stroke];
// 1.绘制原生的图片
[image drawAtPoint:CGPointZero];
// 2.给原生的图片添加文字
NSString *str = @"小码哥";
// 创建字典属性
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[NSForegroundColorAttributeName] = [UIColor redColor];
dict[NSFontAttributeName] = [UIFont systemFontOfSize:20];
[str drawAtPoint:CGPointMake(200, 528) withAttributes:dict];
// 3.生成一张图片给我们,从上下文中获取图片
UIImage *imageWater = UIGraphicsGetImageFromCurrentImageContext();
// 4.关闭上下文
// UIGraphicsEndImageContext();
self.imageView.image = imageWater;
2.图片裁剪
+ (UIImage *)imageWithClipImage:(UIImage *)image borderWidth:(CGFloat)borderWidth borderColor:(UIColor *)color
{
// 图片的宽度和高度
CGFloat imageWH = image.size.width;
// 设置圆环的宽度
CGFloat border = borderWidth;
// 圆形的宽度和高度
CGFloat ovalWH = imageWH + 2 * border;
// 1.开启上下文
UIGraphicsBeginImageContextWithOptions(CGSizeMake(ovalWH, ovalWH), NO, 0);
// 2.画大圆
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, ovalWH, ovalWH)];
[color set];
[path fill];
// 3.设置裁剪区域
UIBezierPath *clipPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(border, border, imageWH, imageWH)];
[clipPath addClip];
// 4.绘制图片
[image drawAtPoint:CGPointMake(border, border)];
// 5.获取图片
UIImage *clipImage = UIGraphicsGetImageFromCurrentImageContext();
// 6.关闭上下文
UIGraphicsEndImageContext();
return clipImage;
}
3.图片截取
//根据手指移动截取任意大小的图片
//1.记录开始节点
//2.添加指定区域截取图片
//3.裁剪图片
// 给控制器的view添加一个pan手势
//记录开始节点
@property (nonatomic, assign) CGPoint startP;
@property (weak, nonatomic) UIImageView *imageV;
@property (nonatomic, weak) UIView *clipView; //设置裁剪显示的View
- (UIView *)clipView{
if (_clipView == nil) {
UIView *view = [[UIView alloc] init];
_clipView = view;
view.backgroundColor = [UIColor blackColor];
view.alpha = 0.5;
[self.view addSubview:view];
}
return _clipView;
}
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
[self.view addGestureRecognizer:pan];
//裁剪
- (void)pan:(UIPanGestureRecognizer *)pan
{
CGPoint endA = CGPointZero;
if (pan.state == UIGestureRecognizerStateBegan) { // 一开始拖动的时候
// 获取一开始触摸点
_startP = [pan locationInView:self.view];
}else if(pan.state == UIGestureRecognizerStateChanged){ // 一直拖动
// 获取结束点
endA = [pan locationInView:self.view];
CGFloat w = endA.x - _startP.x;
CGFloat h = endA.y - _startP.y;
// 获取截取范围
CGRect clipRect = CGRectMake(_startP.x, _startP.y, w, h);
// 生成截屏的view
self.clipView.frame = clipRect;
}
else if (pan.state == UIGestureRecognizerStateEnded){
// 图片裁剪,生成一张新的图片
// 开启上下文
// 如果不透明,默认超出裁剪区域会变成黑色,通常都是透明
UIGraphicsBeginImageContextWithOptions(_imageV.bounds.size, NO, 0);
// 设置裁剪区域
UIBezierPath *path = [UIBezierPath bezierPathWithRect:_clipView.frame];
[path addClip];
// 获取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 把控件上的内容渲染到上下文
[_imageV.layer renderInContext:ctx];
// 生成一张新的图片
_imageV.image = UIGraphicsGetImageFromCurrentImageContext();
// 关闭上下文
UIGraphicsEndImageContext();
// 先移除
[_clipView removeFromSuperview];
// 截取的view设置为nil
_clipView = nil;
}
}
4.屏幕截屏
//1.开启位图上下文
UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, 0);
//2.获取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
//3.把控件上的图层渲染到上下文,layer只能渲染
[view.layer renderInContext:ctx];
//4.生成一张图片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
//5.关闭上下文
UIGraphicsEndImageContext();
5.图片擦除
//在拖动手势中做图片擦除
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
[self.view addGestureRecognizer:pan];
- (void)pan:(UIPanGestureRecognizer *)pan
{
//1.获取当前点
CGPoint curP = [pan locationInView:self.view];
//2.获取擦除的矩形范围
CGFloat wh = 100;
CGFloat x = curP.x - wh * 0.5;
CGFloat y = curP.y - wh * 0.5;
CGRect rect = CGRectMake(x, y, wh, wh);
//3.开启上下文
UIGraphicsBeginImageContextWithOptions(self.view .bounds.size, NO, 0);
//4.获取上下文
CGContextRef ctx=UIGraphicsGetCurrentContext();
//5.把控件的layer渲染上去
[_imageView.layer renderInContext:ctx];
//6.擦除图片
CGContextClearRect(ctx, rect);
//7.生成一张图片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
_imageView.image = image;
//8.关闭上下文
UIGraphicsEndImageContext();
}