Quartz2D
Quartz2D的API是纯C语言的
Quartz2D的API来自于Core Graphics框架
数据类型和函数基本都以CG作为前缀
simple draw
在drawRect:方法中取得上下文后,就可以绘制东西到view上
- View内部有个layer(图层)属性,drawRect:方法中取得的是一个Layer Graphics Context,因此,绘制的东西其实是绘制到view的layer上去了
View之所以能显示东西,完全是因为它内部的layer
绘图的步骤:
- 1.获取上下文
- 2.创建路径(描述路径)
- 3.把路径添加到上下文
- 4.渲染上下文
通常在drawRect方法绘制图形,因为只有在这个方法里面才能获取到跟View的layer相关联的图形上下文
当这个View要显示的时候才会调用drawRect绘制图形,
- 注意:rect是当前控件的bounds
- (void)drawRect:(CGRect)rect {
- first kind
// 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);
- second kind
// 获取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 描述路径
// 设置起点
CGContextMoveToPoint(ctx, 50, 50);
CGContextAddLineToPoint(ctx, 200, 200);
// 渲染上下文
CGContextStrokePath(ctx);
- third kind
// UIKit已经封装了一些绘图的功能
// 贝瑟尔路径
// 创建路径
UIBezierPath *path = [UIBezierPath bezierPath];
// 设置起点
[path moveToPoint:CGPointMake(50, 50)];
// 添加一根线到某个点
[path addLineToPoint:CGPointMake(200, 200)];
// 绘制路径
[path stroke];
- set states
// 获取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 描述路径
//起点
CGContextMoveToPoint(ctx, 50, 50);
CGContextAddLineToPoint(ctx, 100, 50);
// 设置起点
CGContextMoveToPoint(ctx, 80, 60);
// 默认下一根线的起点就是上一根线终点
CGContextAddLineToPoint(ctx, 100, 200);
// 设置绘图状态,一定要在渲染之前
// 颜色
[[UIColor redColor] setStroke];
// 线宽
CGContextSetLineWidth(ctx, 5);
// 设置连接样式
CGContextSetLineJoin(ctx, kCGLineJoinBevel);
// 设置顶角样式
CGContextSetLineCap(ctx, kCGLineCapRound);
// 渲染上下文
CGContextStrokePath(ctx);
- 曲线
// 设置起点
CGContextMoveToPoint(ctx, 50, 50);
// cpx:控制点的x
CGContextAddQuadCurveToPoint(ctx, 150, 20, 250, 50);
- 圆角矩形
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(20, 20, 200, 200) cornerRadius:100];
- 扇形
// 圆弧
// 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 fill];
绘制下载进度
- 拿到滑块,赋值
- (IBAction)progressChange:(UISlider *)sender {
// %% = %
_labelView.text = [NSString stringWithFormat:@"%.2f%%",sender.value * 100];
// 给progressView赋值
_progressView.progress = sender.value;
}
- (void)setProgress:(CGFloat)progress
{
_progress = progress;
// 重新绘制圆弧
// [self drawRect:self.bounds];
// 重绘,系统会先创建与view相关联的上下文,然后再调用drawRect
[self setNeedsDisplay];
}
// 注意:drawRect不能手动调用,因为图形上下文我们自己创建不了,只能由系统帮我们创建,并且传递给我们
- (void)drawRect:(CGRect)rect {
// Drawing code
// 创建贝瑟尔路径
CGFloat radius = rect.size.width * 0.5;
CGPoint center = CGPointMake(radius, radius);
CGFloat endA = -M_PI_2 + _progress * M_PI * 2;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius - 2 startAngle:-M_PI_2 endAngle:endA clockwise:YES];
[path stroke];
}
绘制饼图
- 随机数组
- (NSArray *)arrRandom
{
int totoal = 100;
NSMutableArray *arrM = [NSMutableArray array];
int temp = 0; // 30 40 30
for (int i = 0; i < arc4random_uniform(10) + 1; i++) {
temp = arc4random_uniform(totoal) + 1;
// 随机出来的临时值等于总值,直接退出循环,因为已经把总数分配完毕,没必要在分配。
[arrM addObject:@(temp)];
// 解决方式:当随机出来的数等于总数直接退出循环。
if (temp == totoal) {
break;
}
totoal -= temp;
}
if (totoal) {
[arrM addObject:@(totoal)];
}
return arrM;
}
- 绘制饼图
- (void)drawRect:(CGRect)rect {
// Drawing code
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];
}
}
- 随机颜色
- (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)draw { CGFloat radius = self.bounds.size.width * 0.5; CGPoint center = CGPointMake(radius, radius);
CGFloat startA = 0;
CGFloat angle = 0;
CGFloat endA = 0;
// 第一个扇形
angle = 25 / 100.0 * M_PI * 2;
endA = startA + angle;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
// 添加一根线到圆心
[path addLineToPoint:center];
// 描边和填充通用
[[UIColor redColor] set];
[path fill];
// 第二个扇形
startA = endA;
angle = 25 / 100.0 * M_PI * 2;
endA = startA + angle;
UIBezierPath *path1 = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
// 添加一根线到圆心
[path1 addLineToPoint:center];
// 描边和填充通用
[[UIColor greenColor] set];
[path1 fill];
// 第二个扇形
startA = endA;
angle = 50 / 100.0 * M_PI * 2;
endA = startA + angle;
UIBezierPath *path2 = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
// 添加一根线到圆心
[path2 addLineToPoint:center];
// 描边和填充通用
[[UIColor blueColor] set];
[path2 fill];
}
- 绘制柱状图
(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)attrText
{
// 绘制文字
NSString *str = @"Stay simple,stay navie.";
// 文字的起点
// 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;
// 富文本:给普通的文字添加颜色,字体大小
// drawAtPoint:这个方法不会换行
[str drawAtPoint:CGPointZero withAttributes:textDict];
[str drawInRect:self.bounds withAttributes:textDict];
}
- 绘制图片
// 超出裁剪区域的内容全部裁剪掉
// 注意:裁剪必须放在绘制之前
UIRectClip(CGRectMake(0, 0, 50, 50));
UIImage *image = [UIImage imageNamed:@"001"];
// 默认绘制的内容尺寸跟图片尺寸一样大
// [image drawAtPoint:CGPointZero];
// [image drawInRect:rect];
// 绘图
[image drawAsPatternInRect:rect];
- UIImageView的内部原理
@implementation XMGImageView
- (instancetype)initWithImage:(UIImage *)image
{
// 默认跟图片尺寸一样大
if (self = [super initWithFrame:CGRectMake(0, 0, image.size.width, image.size.height)]) {
_image = image;
}
return self;
}
- (void)setImage:(UIImage *)image
{
_image = image;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
[_image drawInRect:rect];
}
@end
绘制雪花
- 绘图的时候需要用到定时器,通常用CADisplayLink
- NSTimer很少用于绘图,因为调度优先级比较低,并不会准时调用,比较卡顿
CADisplayLink:每次屏幕刷新的时候就会调用,屏幕一般一秒刷新60次
static CGFloat _snowY = 0;
- (void)drawRect:(CGRect)rect {
// 如果以后想绘制东西到view上面,必须在drawRect方法里面,不管有没有手动获取到上下文
// 修改雪花y值
UIImage *image = [UIImage imageNamed:@"雪花"];
[image drawAtPoint:CGPointMake(50, _snowY)];
_snowY += 10;
if (_snowY > rect.size.height) {// 超出屏幕则重来
_snowY = 0;
}
}
- (void)awakeFromNib
{
// 创建定时器
// [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(timeChange) userInfo:nil repeats:YES];
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(timeChange)];
// 控制调用次数
link.frameInterval = 5;
// 添加主运行循环
[link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)timeChange
{
// 注意:这个方法并不会马上调用drawRect,其实这个方法只是给当前控件添加刷新的标记,等下一次屏幕刷新的时候才会调用drawRect
[self setNeedsDisplay];
}
- 图形上下文栈操作
将当前的上下文copy一份,保存到栈顶(那个栈叫做”图形上下文栈”)
void CGContextSaveGState(CGContextRef c)
将栈顶的上下文出栈,替换掉当前的上下文
void CGContextRestoreGState(CGContextRef c)
// 如果以后用贝瑟尔绘制图形【path stroke】,上下文的状态由贝瑟尔路径状态
- (void)drawRect:(CGRect)rect {
// Drawing code
// 1.获取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 2.描述路径
// 第一根
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(10, 125)];
[path addLineToPoint:CGPointMake(240, 125)];
// 把路径添加到上下文
// .CGPath 可以UIkit的路径转换成CoreGraphics路径
CGContextAddPath(ctx, path.CGPath);
// 保存一份上下文的状态
CGContextSaveGState(ctx);
// 设置上下文状态
CGContextSetLineWidth(ctx, 10);
[[UIColor redColor] set];
// 渲染上下文
CGContextStrokePath(ctx);
// 第二根
// 2.描述路径
path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(125, 10)];
[path addLineToPoint:CGPointMake(125, 240)];
// 把路径添加到上下文
// .CGPath 可以UIkit的路径转换成CoreGraphics路径
CGContextAddPath(ctx, path.CGPath);
// 还原状态
CGContextRestoreGState(ctx);
// 渲染上下文
CGContextStrokePath(ctx);
}
- 矩阵操作
- 一般用于绘制有角度的图片
- (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);
}
图片水印
获取上下文,之前的上下文都是在view的drawRect方法中获取(跟View相关联的上下文layer上下文
目前我们需要绘制图片到新的图片上,因此需要用到位图上下文
怎么获取位图上下文,注意位图上下文的获取方式跟layer上下文不一样。位图上下文需要我们手动创建。
// 加载图片
UIImage *image = [UIImage imageNamed:@"小黄人"];
// 开启一个位图上下文,注意位图上下文跟view无关联,所以不需要在drawRect.
// size:位图上下文的尺寸(新图片的尺寸)
// opaque: 不透明度 YES:不透明 NO:透明,通常我们一般都弄透明的上下文
// scale:通常不需要缩放上下文,取值为0,表示不缩放
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
// 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();
- 图片裁剪
- 自定义裁剪圆形图片方法
+ (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;
}
- 屏幕截屏
+ (UIImage *)imageWithCaputureView:(UIView *)view
{
// 开启位图上下文
UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, 0);
// 获取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 把控件上的图层渲染到上下文,layer只能渲染
[view.layer renderInContext:ctx];
// 生成一张图片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// 关闭上下文
UIGraphicsEndImageContext();
return image;
}
- 图片截取
// 添加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;
}
图片擦除
- 设置两张相似的图片
// 获取当前点
CGPoint curP = [pan locationInView:self.view];
// 获取擦除的矩形范围
CGFloat wh = 100;
CGFloat x = curP.x - wh * 0.5;
CGFloat y = curP.y - wh * 0.5;
CGRect rect = CGRectMake(x, y, wh, wh);
// 开启上下文
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 控件的layer渲染上去
[_imageView.layer renderInContext:ctx];
// 擦除图片
CGContextClearRect(ctx, rect);
// 生成一张图片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
_imageView.image = image;
// 关闭上下文
UIGraphicsEndImageContext();