1.介绍
Quartz2D是二维绘图引擎,支持iOS,macOS
在iOS中,体现为CoreGraphics框架
2.作用
绘制图形
绘制文字
绘制/生成图片
读取/生成pdf
截图/裁剪图片
自定义UI控件(手势解锁,统计图)
CGContextRef图形上下文的作用,保存绘制信息,决定输出目标(PDF,Bitmap,显示器窗口)
Quartz2D提供了几种类型的Context
Bitmap Graphics Context
PDF Graphics Context
Window Graphics Context
Layer Graphics Context
Printer Graphics Context
3.iOS中通过继承UIView重写drawRect可以直接获取到Context
-(void)drawRect:(CGRect)rect
{
//rect->bounds,rect指的是该view的bounds
}
4.实现基本图形,文本以及UIImage的绘制
@interface ZSView()
@property(nonatomic,strong) UIImageView * bgImg;
@end
@implementation ZSView
-(instancetype)initWithFrame:(CGRect)frame
{
if(self = [super initWithFrame:frame])
{
self.bgImg=[[UIImageView alloc]initWithFrame:CGRectZero];
[self addSubview:self.bgImg];
}
return self;;
}
//UI绘制的优化
-(void)draw
{
//异步计算UI控件的颜色文字图片大小尺寸数据,然后在主线程上全部渲染到图片上
CGRect rect = self.bounds;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
CGPoint point = CGPointMake(150, 150);
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(ctx, 0.5, 0.5, 0.5, 1);
CGContextFillRect(ctx, CGRectMake(0, 0, 200, 200));
NSMutableDictionary * dict =[NSMutableDictionary dictionary];
[dict setObject:[UIFont systemFontOfSize:15] forKey:NSFontAttributeName];
[dict setObject:[UIColor redColor] forKey:NSForegroundColorAttributeName];
[@"内容区域" drawInRect:CGRectMake(0, 0, 200, 200) withAttributes:dict];
[[UIImage imageNamed:@"release_driver"] drawAtPoint:point];
UIImage *temp = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//CGContextRelease(ctx);
dispatch_async(dispatch_get_main_queue(), ^{
self.bgImg.frame=rect;
self.bgImg.image=temp;
});
});
}
//对于绘制出的图片进行点击区域设置
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
CGPoint location = [[touches anyObject] locationInView:self];
CGRect frame = CGRectMake(0, 0, 200, 200);
if (CGRectContainsPoint(frame, location)) {
NSLog(@"点击");
}
}
-(void)drawRect:(CGRect)rect
{
/*绘制图形*/
//1.绘制直线
//[self drawLine];
//2.绘制曲线
//[self drawCurve];
//3,绘制圆形
//[self drawCircle];
//4.绘制不规则路径
//[self drawPath];
/*绘制控件*/
//1.绘制文字
//[self drawText];
//2.绘制图片
//[self drawImage];
//3.绘制Rect空间
//[self drawRect];
}
#pragma mark - 绘制Rect空间
-(void)drawRect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect rect = CGRectMake(0, 0, 300, 100);
CGContextSetRGBFillColor(ctx, 0, 1.0, 0, 1);//设置填充色
CGContextFillRect(ctx, rect);
CGContextRelease(ctx);
}
#pragma mark - 绘制图片
-(void)drawImage
{
//第一种方式
CGPoint point = CGPointMake(100, 100);
[[UIImage imageNamed:@"release_driver"] drawAtPoint:point];
//第二种方式
CGContextRef ctx = UIGraphicsGetCurrentContext();
UIImage * img = [UIImage imageNamed:@"release_driver"];
//CGContextTranslateCTM(ctx,0.0f,self.frame.size.height);
//CGContextScaleCTM(ctx,1.0,-1.0);
CGContextDrawImage(ctx,CGRectMake(50,50, img.size.width, img.size.height), [img CGImage]);
CGContextRelease(ctx);
}
#pragma mark - 绘制文字
-(void)drawText
{
NSDictionary * dict=@{NSFontAttributeName:[UIFont systemFontOfSize:18],NSForegroundColorAttributeName:[UIColor orangeColor],NSStrokeColorAttributeName:[UIColor blueColor],NSStrokeWidthAttributeName:@2};
[@"绘制文字" drawInRect:CGRectMake(10, 10, 50, 50) withAttributes:dict];
/*也可以通过Lable实现
UILabel * label=[[UILabel alloc]initWithFrame:CGRectMake(10, 10, 100, 50)];
NSString * text=@"绘制文字";
[self.testview addSubview:label];
NSDictionary * dict=@{NSFontAttributeName:[UIFont systemFontOfSize:18],NSForegroundColorAttributeName:[UIColor orangeColor],NSStrokeColorAttributeName:[UIColor blueColor],NSStrokeWidthAttributeName:@2};
NSAttributedString * str=[[NSAttributedString alloc]initWithString:text attributes:dict];
label.attributedText=str;
*/
}
#pragma mark - 绘制路径
-(void)drawPath
{
//1.获取上下文
CGContextRef context = UIGraphicsGetCurrentContext();
//2.创建路径
CGMutablePathRef path = CGPathCreateMutable();//创建路径
CGPathMoveToPoint(path, nil, 100, 100);//移动到指定位置(设置路径起点)
CGPathAddLineToPoint(path, nil, 200, 300);//绘制直线(从起始位置开始)
CGPathAddLineToPoint(path, nil,50, 100);//绘制直线(从起始位置开始)
CGContextAddPath(context, path);//把路径添加到上下文(画布)中
//3.设置图形上下文状态属性
CGContextSetRGBStrokeColor(context, 1.0, 0, 0, 1);//设置笔触颜色
CGContextSetRGBFillColor(context, 0, 1.0, 0, 1);//设置填充色
CGContextSetLineWidth(context, 5.0);//设置线条宽度
//4.绘制路径
CGContextDrawPath(context, kCGPathStroke);//最后一个参数是填充类型
//5.释放路径和上下文资源
CGPathRelease(path);
CGContextRelease(context);
}
#pragma mark - 绘制曲线
-(void)drawCurve
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(context, 100, 100);
CGContextAddCurveToPoint(context, 200, 0, 300, 100, 100, 100);
CGContextSetRGBStrokeColor(context, 0.5, 0.4, 0.3, 1);
CGContextSetRGBFillColor(context, 0.3, 0.4, 0.5, 1);
CGContextSetLineWidth(context, 1.0);
CGContextDrawPath(context, kCGPathStroke);
CGContextRelease(context);
}
#pragma mark - 绘制圆形
-(void)drawCircle
{
//1.获取上下文
CGContextRef context = UIGraphicsGetCurrentContext();
//2.创建圆形路径
CGContextAddArc(context, 100, 100, 50, 0, 2*M_PI, 0);
//3.设置上下文属性
CGContextSetRGBStrokeColor(context, 0.5, 0.4, 0.3, 1);//设置笔触颜色
CGContextSetRGBFillColor(context, 0.3, 0.4, 0.5, 1);//设置填充色
CGContextSetLineWidth(context, 1.0);//设置线条宽度
//4.绘制路径
CGContextDrawPath(context, kCGPathFill);
//5.释放资源
CGContextRelease(context);
}
#pragma mark - 绘制直线
-(void)drawLine
{
//1.获取上下文
CGContextRef context = UIGraphicsGetCurrentContext();
//2.创建路径
CGContextMoveToPoint(context, 100, 100);
CGContextAddLineToPoint(context, 200, 200);
//3.设置图形上下文状态属性
CGContextSetRGBStrokeColor(context, 1.0, 0, 0, 1);//设置笔触颜色
CGContextSetRGBFillColor(context, 0, 1.0, 0, 1);//设置填充色
CGContextSetLineWidth(context, 5.0);//设置线条宽度
CGContextSetLineCap(context, kCGLineCapButt);//设置顶点样式
CGContextSetLineJoin(context, kCGLineJoinMiter);//设置连接点样式
CGFloat lengths[5] = {18,9,6,3,1};
CGContextSetLineDash(context, 0, lengths, 5);
CGContextSetShadowWithColor(context, CGSizeMake(2, 2), 0, [UIColor blueColor].CGColor);
//4.绘制路径
CGContextDrawPath(context, kCGPathFillStroke);//最后一个参数是填充类型
//5.释放上下文资源
CGContextRelease(context);
}
#pragma mark - 绘制直线(第二种方式),使用UIBezierPath替代CGPathRef
-(void)drawLine2
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 3);
[[UIColor blackColor]setStroke];
//UIBezierPath是对CGPathRef的面向对象封装
UIBezierPath * path=[UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, 0)];
[path addLineToPoint:CGPointMake(50,50)];
CGContextAddPath(context, path.CGPath);
CGContextStrokePath(context);
}
#pragma mark - 绘制直线(第三种方式),直接使用UIBezierPath
-(void)drawLine3
{
UIBezierPath * path=[UIBezierPath bezierPathWithRect:CGRectMake(10, 10, 30, 30)];
//内部帮我们实现获取CGContextRef和设置Path的功能
[path stroke];
}
4.Context上下文状态栈
Context的状态(颜色,笔宽等属性)可以保存在栈中,可以保存多个状态到状态栈,但是只能取栈顶状态到上下文中使用。
绘制两条不同颜色的线条
CGContextRef context = UIGraphicsGetCurrentContext();
//设置上下文状态
CGContextSetLineWidth(context, 3);
[[UIColor blackColor]setStroke];
//把该上下文状态存储到栈中(栈中1元素,这次状态位于栈顶)
CGContextSaveGState(context);
//再次设置上下文状态(覆盖当前上下文状态)
CGContextSetLineWidth(context, 3);
[[UIColor redColor]setStroke];
//把该上下文状态存储到栈中(栈中2元素,这次状态位于栈顶)
CGContextSaveGState(context);
//使用当前上下文状态描绘路径(Red)
UIBezierPath * path=[UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, 0)];
[path addLineToPoint:CGPointMake(50,50)];
CGContextAddPath(context, path.CGPath);
CGContextStrokePath(context);
UIBezierPath * path2=[UIBezierPath bezierPath];
[path2 moveToPoint:CGPointMake(80, 80)];
[path2 addLineToPoint:CGPointMake(80,20)];
CGContextAddPath(context, path2.CGPath);
//把(Black)状态从栈取出到栈顶并设置上下文状态
CGContextRestoreGState(context);
CGContextRestoreGState(context);
CGContextStrokePath(context);
5.应用
a.合成图片(给图片加水印)
//使用BitmapContext上下文
UIImage * bgimage=[UIImage imageNamed:@"bgimg"];
UIImage * logo=[UIImage imageNamed:@"logo"];
//开始上下文
UIGraphicsBeginImageContext(bgimage.size);
//把两张图片渲染到上下文中
[bgimage drawAtPoint:CGPointZero];
[logo drawAtPoint:CGPointMake(50, 50)];
//合成图像
UIImage * newImage=UIGraphicsGetImageFromCurrentImageContext();
//结束上下文绘制
UIGraphicsEndImageContext();
UIImageView * imgV=[[UIImageView alloc]initWithFrame:self.bounds];
[self addSubview:imgV];
imgV.image=newImage;
/*更好的方式是进行异步绘制
UIImageView * imgV=[[UIImageView alloc]initWithFrame:self.bounds];
[self addSubview:imgV];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
UIImage * bgimage=[UIImage imageNamed:@"bgimg"];
UIImage * logo=[UIImage imageNamed:@"logo"];
UIGraphicsBeginImageContext(bgimage.size);
[bgimage drawAtPoint:CGPointZero];
[logo drawAtPoint:CGPointMake(50, 50)];
UIImage * newImage=UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
imgV.image=newImage;
});
});
*/
b.裁剪圆形图片
UIImage * bgimage=[UIImage imageNamed:@"bgimg"];
UIGraphicsBeginImageContext(bgimage.size);
//设置裁剪区域
UIBezierPath * path=[UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, bgimage.size.width, bgimage.size.height)];
[path addClip];
//把图片渲染到上下文中
[bgimage drawAtPoint:CGPointZero];
UIImage * newImage=UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView * imgV=[[UIImageView alloc]initWithFrame:self.bounds];
[self addSubview:imgV];
imgV.image=newImage;
c.裁剪带边框的圆形图片
UIImage * bgimage=[UIImage imageNamed:@"bgimg"];
CGSize contextSize=CGSizeMake(bgimage.size.width+20, bgimage.size.height+20);
//设置上下文大小比图片宽度大10
UIGraphicsBeginImageContext(contextSize);
//填充一个橙色的圆形
UIBezierPath * path=[UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, contextSize.width, contextSize.height)];
[[UIColor orangeColor]set];
[path fill];
//设置裁剪区域
UIBezierPath * path2=[UIBezierPath bezierPathWithOvalInRect:CGRectMake(10, 10, bgimage.size.width, bgimage.size.height)];
[path2 addClip];
//把图片渲染到上下文中
[bgimage drawAtPoint:CGPointMake(10, 10)];
UIImage * newImage=UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView * imgV=[[UIImageView alloc]initWithFrame:self.bounds];
[self addSubview:imgV];
imgV.image=newImage;
d.截屏
/*
第二个参数表示图像上下文是不是要透明-YES不透明。
[UIScreen mainScreen].scale是像素和点的一个桥接,在iOS-UIKit中使用点作为单位,其实内部就是像素*scale的结果
[UIScreen mainScreen].scale-iphone8-2
[UIScreen mainScreen].scale-iphone8p-3
值得注意的是CoreGraphics框架使用的是像素作为单位
*/
UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, [UIScreen mainScreen].scale);
//UIGraphicsBeginImageContext(self.bounds.size);
CGContextRef ctx=UIGraphicsGetCurrentContext();
//把Layer被渲染到CGContext上
[self.layer renderInContext:ctx];
UIImage * newImage=UIGraphicsGetImageFromCurrentImageContext();
NSData * data=UIImagePNGRepresentation(newImage);
[data writeToFile:@"/Users/zhousiyang/Downloads/1.png" atomically:YES];
UIGraphicsEndImageContext();
e.拖拽截图
@interface TestViewController ()
{
UIView * maskView;
CGPoint startP;
}
@property (weak, nonatomic) IBOutlet UIImageView *bgImgV;
@end
@implementation TestViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIPanGestureRecognizer * pan=[[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
[self.bgImgV addGestureRecognizer:pan];
}
-(void)pan:(UIPanGestureRecognizer*)pan
{
if(pan.state==UIGestureRecognizerStateBegan)
{
startP=[pan locationInView:self.bgImgV];
}
if(pan.state==UIGestureRecognizerStateChanged)
{
CGPoint panP=[pan translationInView:self.bgImgV];
CGFloat x=startP.x;
CGFloat y=startP.y;
CGFloat w=panP.x;
CGFloat h=panP.y;
if(maskView==nil)
{
maskView=[[UIView alloc]initWithFrame:CGRectMake(x, y, w, h)];
maskView.backgroundColor=[UIColor colorWithWhite:0.5 alpha:0.5];
[self.bgImgV addSubview:maskView];
}else
{
maskView.frame=CGRectMake(x, y, w, h);
}
}
if(pan.state==UIGestureRecognizerStateEnded)
{
[self screencapture];
[maskView removeFromSuperview];
maskView=nil;
}
}
-(void)screencapture
{
//1.开启图片上下文(大小就是被截图View大小)
UIGraphicsBeginImageContextWithOptions(self.bgImgV.bounds.size, YES, 0.0);
NSLog(@"1%@",NSStringFromCGSize(self.bgImgV.bounds.size));
//2.设置Clip区域
UIBezierPath * path=[UIBezierPath bezierPathWithRect:CGRectMake(maskView.frame.origin.x, maskView.frame.origin.y, maskView.frame.size.width, maskView.frame.size.height)];
[path addClip];
//或者UIRectClip(maskView.frame);
CGContextRef ctx=UIGraphicsGetCurrentContext();
//3.把图片渲染到上下文中
[self.bgImgV.layer renderInContext:ctx];
//4.获取上下文中的图片(大小图片上下文大小-被截图View大小)
UIImage * newImage=UIGraphicsGetImageFromCurrentImageContext();
NSLog(@"2%@",NSStringFromCGSize(newImage.size));
UIGraphicsEndImageContext();
self.bgImgV.image=newImage;
}
f.手势解锁
思路:
布局一个有9个按钮固定大小View的解锁界面,界面外边不响应滑动事件;(事件响应
滑动到按钮范围内,按钮变为选中状态,把滑到过的按钮添加到数组中;(touches...
把数组中的按钮按照中心点连接绘制起来,末端加一个最后绘制的点;(drawRect,UIBezierPath
绘制完毕保存按钮的tag;(touchesEnd
@interface LockView()
{
BOOL isEnd;
CGPoint curP;
NSMutableArray * btnArr;
}
@end
@implementation LockView
-(void)awakeFromNib
{
[super awakeFromNib];
btnArr=[NSMutableArray array];
for (int i=0; i<9; i++) {
UIButton * btn=[[UIButton alloc]init];
[btn setUserInteractionEnabled:NO];
btn.tag=i;
btn.backgroundColor=[UIColor colorWithWhite:0.5 alpha:0.5];
btn.titleLabel.font=[UIFont systemFontOfSize:13];
[btn setTitle:[NSString stringWithFormat:@"%d未选中",i] forState:UIControlStateNormal];
[btn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[btn setTitle:[NSString stringWithFormat:@"%d选中",i] forState:UIControlStateSelected];
[btn setTitleColor:[UIColor blueColor] forState:UIControlStateSelected];
[self addSubview:btn];
}
}
-(void)layoutSubviews
{
float border=(self.frame.size.width-150)/4;
for (int i=0; i<9; i++) {
float x=border+(border+50)*(i%3);
float y=border+(border+50)*(i/3);
float w=50;
float h=50;
UIButton * btn=self.subviews[i];
btn.frame=CGRectMake(x, y, w, h);
}
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//点击在按钮范围,按钮被选中
UITouch * touch=[touches anyObject];
CGPoint startP=[touch locationInView:self];
for (UIButton * btn in self.subviews) {
if(CGRectContainsPoint(btn.frame, startP))
{
[btnArr addObject:btn];
btn.selected=YES;
break;
}
}
}
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//滑动在按钮范围,按钮被选中
UITouch * touch=[touches anyObject];
curP=[touch locationInView:self];
for (UIButton * btn in self.subviews) {
if(CGRectContainsPoint(btn.frame,curP)&&btn.selected==NO)
{
[btnArr addObject:btn];
btn.selected=YES;
break;
}
}
//调用drawRect绘制线条
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
isEnd=YES;
[self setNeedsDisplay];
}
-(void)drawRect:(CGRect)rect
{
if(btnArr.count>0)
{
CGContextRef ctx=UIGraphicsGetCurrentContext();
[[UIColor redColor]set];
CGContextSetLineJoin(ctx, kCGLineJoinRound);
CGContextSetLineWidth(ctx, 10);
UIBezierPath * path=[UIBezierPath bezierPath];
for (int i=0; i<btnArr.count; i++) {
UIButton * btn=btnArr[i];
if(i==0)
{
[path moveToPoint:btn.center];
}else
{
[path addLineToPoint:btn.center];
}
}
if(!isEnd)
{
[path addLineToPoint:curP];
}
CGContextAddPath(ctx, path.CGPath);
CGContextStrokePath(ctx);
/*
直接使用UIBezierPath
[[UIColor redColor]set];
UIBezierPath * path=[UIBezierPath bezierPath];
path.lineWidth=10;
path.lineJoinStyle=kCGLineJoinRound;
for (int i=0; i<btnArr.count; i++) {
UIButton * btn=btnArr[i];
if(i==0)
{
[path moveToPoint:btn.center];
}else
{
[path addLineToPoint:btn.center];
}
}
if(!isEnd)
{
[path addLineToPoint:curP];
}
[path stroke];
*/
}
}
g.画板
思路:
手滑过的路径可以通过手势或者touch...方法监听。
绘制:每条路径通过UIBezierPath,所有路径保存在数组中,进行drawRect重绘
颜色和线宽:通过UIBezierPath属性修改,颜色通过上下文修改
撤销:删除数组末位元素,重绘
清屏:删除数组全部元素,重绘
橡皮擦:使用画板底色画笔绘制
优化:通过 [self.drawview setNeedsDisplay]以及drawRect重绘(CPU),如果绘制很多很多线条会导致内存和CPU暴增,思路是把绘制的Path变成一张图片,在手势移动的时候绘制上去。
CAShapeLayer * shaperLayer=[[CAShapeLayer alloc]init];
shaperLayer.path=path.CGPath;
[self.view.layer addSublayer:shaperLayer];
h.抽奖转盘
关键点:
布局UI,键盘上每个小格子如果使用UIButton,要设置anchorPoint=(0.5,1),position=背景View.center。
旋转动画的交互,使用UIView动画,不能使用CA核心动画。
从大切图截取部分图片的问题,使用CoreGrapyics框架问题。
UIImage * img=[UIImage imageNamed:@""];
float w=img.size.width;
float h=img.size.height;
//CoreGraphyics框架是像素点为单位,截取时必须乘以[UIScreen mainScreen].scale
CGImageCreateWithImageInRect(img.CGImage, CGRectMake(0, 0, w*[UIScreen mainScreen].scale, h*[UIScreen mainScreen].scale));