×

Quartz2D

96
PurpleWind
2016.09.18 20:58* 字数 6958

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];

画矩形、正方形

  • (void)drawRect:(CGRect)rect {
    //1.获取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //2.描述路径
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(50, 50, 200, 200)];
    //3.把路径添加到上下文
    CGContextAddPath(ctx, path.CGPath);

    [[UIColor redColor] set];// 路径的颜色

    //4.把上下文的内容渲染到View的layer.
    // CGContextStrokePath(ctx);// 描边路径
    CGContextFillPath(ctx);// 填充路径

}

描边矩形.png

填充矩形.png
画扇形

  • (void)drawRect:(CGRect)rect {

    CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
    CGFloat radius = rect.size.width * 0.5 - 10;
    CGFloat startA = 0;
    CGFloat endA = -M_PI_2;
    // 画弧的路径
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:NO];
    // 添加一根线到圆心
    [path addLineToPoint:center];
    // 闭合路径
    [path closePath];
    // 路径颜色
    [[UIColor redColor] set];
    // 填充路径
    [path fill];
    // 描边路径
    // [path stroke];

}

描边扇形.png

填充扇形.png
画圆形

  • (void)drawRect:(CGRect)rect {
    //1.获取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //2.描述路径
    // cornerRadius:圆角半径。矩形的宽高都为200,如果圆角为100,那么两个角之间弧线上任意一点到矩形中心的距离都为100,所以为圆形
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 50, 200, 200) cornerRadius:100];
    //3.把路径添加到上下文
    CGContextAddPath(ctx, path.CGPath);

    [[UIColor redColor] set];// 路径的颜色

    //4.把上下文的内容渲染到View的layer.
    // CGContextStrokePath(ctx);// 描边路径
    CGContextFillPath(ctx);// 填充路径

}

描边圆形.png

填充圆形.png
画圆角矩形

  • (void)drawRect:(CGRect)rect {
    //1.获取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //2.描述路径
    // cornerRadius:圆角半径。
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 50, 200, 200) cornerRadius:50];
    //3.把路径添加到上下文
    CGContextAddPath(ctx, path.CGPath);

    [[UIColor redColor] set];// 路径的颜色

    //4.把上下文的内容渲染到View的layer.
    CGContextStrokePath(ctx);// 描边路径
    // CGContextFillPath(ctx);// 填充路径

}

描边圆角矩形.png

填充圆角矩形.png
画直线

  • (void)drawRect:(CGRect)rect {

    //1.获取跟View相关联的上下文(uigraphics开头)
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    //2.描述路径
    //一条路径可以绘制多条线 路径:path 路径绘制多条线:path使用了两次(2次的起点到终点),都是将线添加到某个点
    UIBezierPath *path = [UIBezierPath bezierPath];
    //设置起点
    [path moveToPoint:CGPointMake(50, 150)];
    //添加一根线Line到某个点
    [path addLineToPoint:CGPointMake(250, 50)];

    //画第二根线
    [path moveToPoint:CGPointMake(50, 250)];
    [path addLineToPoint:CGPointMake(250, 100)];

//设置线宽
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

  • (void)viewDidLoad {

    //创建充满整个屏幕的自定义View 并在自定义View中绘制图形
    _customView = [[CustomView alloc] initWithFrame:self.view.bounds];
    _customView.backgroundColor = [UIColor greenColor];
    [self.view addSubview:_customView];

    //给自定义View添加点击事件
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(TapGestureRecognizer:)];
    [_customView addGestureRecognizer:tap];

}

  • (void)TapGestureRecognizer:(UITapGestureRecognizer *)gesture{
    // 获得手指在当前View上的点(位置)
    CGPoint tapPoint = [gesture locationInView:_customView];

    //判断点是否在绘制的路径内部
    if ([_customView.bezierPath containsPoint:tapPoint]){
    _customView.fillColor = [UIColor yellowColor];
    // 重绘
    [_customView setNeedsDisplay];
    }
    }

@end

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

本塞尔曲线原理

贝塞尔曲线.gif

  • (void)drawRect:(CGRect)rect {
    //1.获取跟View相关联的上下文.
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    //2.描述路径
    UIBezierPath *path = [UIBezierPath bezierPath];
    //画曲线,设置起点.还有一个控制点(用来控制曲线的方向跟弯曲程度)
    //设置起点.
    [path moveToPoint:CGPointMake(10, 150)];
    //添加一要曲线到某个点
    [path addQuadCurveToPoint:CGPointMake(200, 150) controlPoint:CGPointMake(150, 10)];
    //3.把路径添加到上下文当中.
    CGContextAddPath(ctx, path.CGPath);
    //4.把上下文的内容渲染View上.
    CGContextStrokePath(ctx);

}

曲线.png
画饼图

做法1:

  • (void)drawRect:(CGRect)rect {
    NSArray *dataArray = @[@25,@25,@50];
    // 画弧
    CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
    // 半径
    CGFloat radius = rect.size.width * 0.5 - 10;

    CGFloat startA = 0;
    CGFloat angle = 0;
    CGFloat endA = 0;

    for (NSNumber *num in dataArray) {
    startA = endA;
    // 遍历出第一个对象25,angle =25/100 *2π,即angle = π/2,所以为1/4圆,
    angle = num.intValue / 100.0 * M_PI * 2;
    // 截至角度= 开始的角度+ 遍历出的对象所占整个圆形的角度
    endA = startA + angle;
    // 顺势针画贝塞尔曲线
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
    // 设置随机颜色
    [[self randomColor] set];
    // 添加一根线到圆心
    [path addLineToPoint:center];
    // 填充路径
    // [path fill];
    // 描边路径
    [path stroke];
    }

}

描边饼图.png

填充饼图.png

  • (void)drawRect:(CGRect)rect {
    NSArray *dataArray = @[@25,@25,@50];
    // 画弧
    CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
    // 半径
    CGFloat radius = rect.size.width * 0.5 - 10;
    CGFloat startA = 0;
    CGFloat angle = 0;
    CGFloat endA = 0;

    for (NSNumber *num in dataArray) {

      startA = endA;
      // 遍历出第一个对象25,angle =25/100 *2π,即angle = π/2,所以为1/4圆,
      angle = num.intValue / 100.0 * M_PI * 2;
      // 截至角度= 开始的角度+ 遍历出的对象所占整个圆形的角度
      endA = startA + angle;
      // 顺势针画贝塞尔曲线
      UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
      // 设置随机颜色
      [[self randomColor] set];
      // 添加一根线到圆心
      [path addLineToPoint:center];
      // 填充路径
     [path fill];
      // 描边路径
    

// [path stroke];
}
}

//随机生成一个颜色

  • (UIColor *)randomColor {

    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)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    //重绘
    [self setNeedsDisplay];

}

重绘.gif
做法2:

  • (void)drawRect:(CGRect)rect {

    CGPoint center = CGPointMake(self.bounds.size.width * 0.5, self.bounds.size.height * .5);
    CGFloat radius = self.bounds.size.width * 0.5 - 10;
    CGFloat startA = 0;
    CGFloat endA = 25 / 100.0 * M_PI * 2;
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
    [[UIColor redColor] set];
    //添加一根线到圆心
    [path addLineToPoint:center];
    [path fill];

    //第二个扇形
    startA = endA;
    CGFloat angle = 25 / 100.0 * M_PI * 2;
    endA = startA + angle;
    UIBezierPath *path2 = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
    [[UIColor greenColor] set];
    //添加一根线到圆心
    [path2 addLineToPoint:center];
    [path2 fill];

    startA = endA;
    angle = 50 / 100.0 * M_PI * 2;
    endA = startA + angle;
    UIBezierPath *path3 = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
    [[UIColor blueColor] set];
    //添加一根线到圆心
    [path3 addLineToPoint:center];
    [path3 fill];}

//随机生成一个颜色

  • (UIColor *)randomColor {

    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)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    //重绘
    [self setNeedsDisplay];

}

饼图-做法2.png
画文字

  • (void)drawRect:(CGRect)rect {
    NSString *str = @"简书:CoderZb";

    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    //设置字体
    dict[NSFontAttributeName] = [UIFont systemFontOfSize:30];
    //设置颜色
    dict[NSForegroundColorAttributeName] = [UIColor redColor];
    //设置描边
    dict[NSStrokeColorAttributeName] = [UIColor blueColor];
    dict[NSStrokeWidthAttributeName] = @3;
    //设置阴影
    NSShadow *shadow = [[NSShadow alloc] init];
    shadow.shadowColor = [UIColor greenColor];
    shadow.shadowOffset = CGSizeMake(-2, -2);
    shadow.shadowBlurRadius = 3;
    dict[NSShadowAttributeName] = shadow;

    //设置文字的属性
    //drawAtPoint不会自动换行
    //[str drawAtPoint:CGPointMake(0, 0) withAttributes:dict];
    //drawInRect会自动换行
    [str drawInRect:self.bounds withAttributes:dict];

}

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

ViewController.m文件

import "ViewController.h"

import "ZBImageView.h"

@interface ViewController ()
@end

@implementation ViewController

  • (void)viewDidLoad {
    [super viewDidLoad];
    ZBImageView *ImageV = [[ZBImageView alloc] init];
    ImageV.frame = CGRectMake(0, 0,self.view.frame.size.width, 400);
    ImageV.image = [UIImage imageNamed:@"AA"];
    [self.view addSubview:ImageV];
    }
    @end
    ZBImageView.h文件

import <UIKit/UIKit.h>

@interface ZBImageView : UIView

@property (nonatomic ,strong) UIImage *image;

@end
ZBImageView.m文件

import "ZBImageView.h"

@implementation ZBImageView

  • (void)setImage:(UIImage *)image {
    _image = image;
    //重绘
    [self setNeedsDisplay];
    }

  • (void)drawRect:(CGRect)rect {

    [self.image drawInRect:rect];
    NSLog(@"%s",func);
    }

@end

import "VCView.h"

@implementation VCView

  • (void)awakeFromNib {

    //添加定时器
    //[NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(update) userInfo:nil repeats:YES];

    //多久调用update方法?当下一次屏幕刷新时调用(屏幕每一秒刷新30-60秒次,所以1秒调用update方法大概30-60次)
    CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];

    //想要让CADisplayLink工作, 必须得要添加到运行循环当中.
    [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

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

}

static int _snowY = 0;

  • (void)update {

    NSLog(@"%s",func);
    _snowY += 10;
    if (_snowY > self.bounds.size.height) {
    _snowY = 0;
    }
    //重绘
    [self setNeedsDisplay];

}

  • (void)drawRect:(CGRect)rect {

    //加载图片
    UIImage *image = [UIImage imageNamed:@"雪花"];
    [image drawAtPoint:CGPointMake(0, _snowY)];

}

@end

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

  • (void)drawRect:(CGRect)rect {
    // Drawing code

    //1.获取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(200, 100, 200, 100)];
    [[UIColor redColor] set];

    //缩放
    CGContextScaleCTM(ctx, 0.5, 0.5);
    //移动
    CGContextTranslateCTM(ctx, 150,200);

    //旋转
    CGContextRotateCTM(ctx, M_PI_4);

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

}

原始.png

缩放.png

移动.png

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

  • (void)awakeFromNib {
    //方式1:NSTimer定时器
    //[NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(update) userInfo:nil repeats:YES];

    //方式2:CADisplayLink. 多久调用update方法?当下一次屏幕刷新时调用(屏幕每一秒刷新30-60秒次,所以1秒调用update方法大概30-60次)
    CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];

    //想要让CADisplayLink工作, 必须得要添加到运行循环当中.
    [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

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

}

static int _snowY = 0;

  • (void)update {

    NSLog(@"%s",func);
    _snowY += 10;
    if (_snowY > self.bounds.size.height) {
    _snowY = 0;
    }
    //重绘
    [self setNeedsDisplay];

}

  • (void)drawRect:(CGRect)rect {

    //加载图片
    UIImage *image = [UIImage imageNamed:@"雪花"];
    [image drawAtPoint:CGPointMake(0, _snowY)];

}

雪花.gif
画水印

import "ViewController.h"

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

@end

@implementation ViewController

  • (void)viewDidLoad {
    [super viewDidLoad];

    //生成一张图片
    //0.加载图片
    UIImage *oriImage = [UIImage imageNamed:@"壁纸"];
    //1.创建位图上下文(size:开启多大的上下文,就会生成多大的图片)
    UIGraphicsBeginImageContext(oriImage.size);
    //2.把图片绘制到上下文当中
    [oriImage drawAtPoint:CGPointZero];
    //3.绘制水印(虽说UILabel可以快速实现这种效果,但是我们也可以绘制出来)
    NSString *str = @"简书:CoderZb";

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

  • (void)viewDidLoad {
    [super viewDidLoad];

    //1.确定边框的宽度
    CGFloat borderW = 10;
    //2.加载图片
    UIImage *oriImage = [UIImage imageNamed:@"头像"];
    //3.开启位图上下文(大小 原始图片的宽高度+ 2 *边框宽度)
    CGSize size = CGSizeMake(oriImage.size.width + 2 * borderW, oriImage.size.height + 2 * borderW);
    UIGraphicsBeginImageContext(size);
    //4.绘制边框(大圆)
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, size.width, size.height)];
    [[UIColor yellowColor] set];
    [path fill];
    //5.绘制小圆(把小圆设置成裁剪区域)
    UIBezierPath *clipPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(borderW, borderW, oriImage.size.width, oriImage.size.height)];
    [clipPath addClip];
    //6.把图片绘制到上下文当中
    [oriImage drawAtPoint:CGPointMake(borderW, borderW)];
    //7.从上下文当中生成图片
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    //8.关闭上下文.
    UIGraphicsEndImageContext();
    //9.显示新图片
    self.imageV.image = newImage;
    }

裁剪图片.png
屏幕截图

import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

  • (void)viewDidLoad {
    [super viewDidLoad];
    }

-(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;

}

  • (IBAction)pan:(UIPanGestureRecognizer *)pan {

    //获取当前手指所在的点
    CGPoint curP = [pan locationInView:self.imageV];
    // 判断手势的状态
    // 要执行2,那么1必须执行过。联想一下点击图片的过程就理解了。
    // 要执行3,那么1,2也必须执行过。
    if(pan.state == UIGestureRecognizerStateBegan) {// 1
    //记录当前手指的开始点
    self.startP = curP;

    } else if(pan.state == UIGestureRecognizerStateChanged) {// 2

      //rect
      CGFloat w = curP.x - self.startP.x;
      CGFloat h = curP.y - self.startP.y;
      CGRect rect = CGRectMake(self.startP.x, self.startP.y, w, h);
    
      self.coverView.frame = rect;
    

    }else if(pan.state == UIGestureRecognizerStateEnded) {// 3

    //生成一张图片
    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

  • (void)viewDidLoad {
    [super viewDidLoad];

    self.imageV.userInteractionEnabled = YES;

    //添加手势
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    [self.imageV addGestureRecognizer:pan];

}

  • (void)pan:(UIPanGestureRecognizer *)pan {

    CGFloat rectWH = 20;
    //获取当前手指的点
    CGPoint curP = [pan locationInView:self.imageV];
    // 当手指在(curP.x,curP.y)的位置上时,就让这个位置的x和y之分别减去10像素,由此CGRectMake(x, y, rectWH, rectWH)构成的形状就是正方形,以后把这个正方形作为擦除的区域
    CGFloat x = curP.x - rectWH * 0.5;
    CGFloat y = curP.y - rectWH * 0.5;
    CGRect rect = CGRectMake(x, y, rectWH, rectWH);

//开启一个位图上下文
//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

  • (NSMutableArray *)selectBtnArray {

    if (_selectBtnArray == nil) {
    _selectBtnArray = [NSMutableArray array];
    }
    return _selectBtnArray;
    }

  • (void)awakeFromNib {
    //添加子控件
    [self setUp];
    }

  • (instancetype)initWithFrame:(CGRect)frame
    {
    self = [super initWithFrame:frame];
    if (self) {
    //添加子控件
    [self setUp];
    }
    return self;
    }

//添加子控件

  • (void)setUp {

    for (int i = 0; i < 9; i++) {

      //创建按钮
      UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
      btn.userInteractionEnabled = NO;
    
      //如果写成btn.userInteractionEnabled=YES或者不写,那么点击按钮的响应事件由按钮的高亮状态响应(处理)了,就不会传递给父控件(上一个响应者)处理了,也就不会执行父控件所在的类中的touchesBegan方法了.
    
      //如果写成btn.userInteractionEnabled=NO,即不让按钮与用户交互,响应事件传递给父控件(ClockView),又因为在ClockView类里面写了touchesBegan方法,所以会执行touchesBegan方法
      btn.tag = i;
      //设置按钮的图片
      [btn setImage:[UIImage imageNamed:@"空心圆圈"] forState:UIControlStateNormal];
    
      //设置选中状态下的图片
      [btn setImage:[UIImage imageNamed:@"gesture_node_selected"] forState:UIControlStateSelected];
    
      [self addSubview:btn];
    

    }
    }

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

  • (CGPoint)getCurPoint:(NSSet )touches {//CGPoint后面没有,强烈注意
    //1获取当前手指的点

    //1.1当用户用一根手指触摸屏幕时,会创建一个与手指相关联的UITouch对象
    UITouch *touch = [touches anyObject];
    //1.2手指在当前view上的位置
    CGPoint curP = [touch locationInView:self];
    return curP;
    }

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

  • (UIButton *)btnContainsPoint:(CGPoint)point {
    //取出所有的子控件.
    for (UIButton *btn in self.subviews) {
    // 判断当前点在不在按钮身上.
    if (CGRectContainsPoint(btn.frame, point)) {

          //如果在的话, 让按钮成为选中状态
    
          return btn;
      }
    

    }
    return nil;
    }

//手指开始点击

  • (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //获取当前手指的点
    CGPoint curP = [self getCurPoint:touches];
    //给定一个点,判断这个点在不在按钮身上
    UIButton *btn = [self btnContainsPoint:curP];
    if(btn && btn.selected == NO) {
    btn.selected = YES;
    //保存选中的按钮
    [self.selectBtnArray addObject:btn];
    }

}

//手指移动

  • (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    //获取当前手指的点
    CGPoint curP = [self getCurPoint:touches];

    //在手指移动的过程中,边移动手指,边用self.curP保存当前手指的点
    self.curP = curP;

    //取出所有的子控件.
    //给定一个点,判断这个点在不在按钮身上
    UIButton *btn = [self btnContainsPoint:curP];
    if(btn && btn.selected == NO) {
    btn.selected = YES;
    //保存选中的按钮
    [self.selectBtnArray addObject:btn];
    }

    //重绘
    [self setNeedsDisplay];
    }

//手指离开

  • (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
    //所有选中按钮取消选中状态

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:@"手势错误"];
 }

}

  • (void)drawRect:(CGRect)rect {

    if (self.selectBtnArray.count) {//view开始显示之前会调用drawRect方法,此时方法里面没有任何值,数组里面也没有值,所以要先判断,if (self.selectBtnArray.count),如果没有值,就不执行if里面的内容

      //描述路径
      UIBezierPath *path = [UIBezierPath bezierPath];
      //取出所有选中的按钮
    
      for (int i = 0; i < self.selectBtnArray.count; i++) {
          //取出每一个按钮
          UIButton *btn =  self.selectBtnArray[i];
    
          if (i == 0) {// selectBtnArray数组中的第0个对象
              //让selectBtnArray数组中的第0个对象按钮成为路径的起点。第0个对象是9个按钮中的任意一个,因为点击哪个按钮由用户自己决定的。
              [path moveToPoint:btn.center];
          }else {// 不是selectBtnArray数组中的第0个元素
              // 如果不是起点,说明起点在之前已经确定了,我们只需要划线到第1个对象按钮。同样,这第一个对象按钮也是9个按钮中的任意一个
              [path addLineToPoint:btn.center];//1
          }
          //区分1和2:1在手指拖动的过程中不显示线,拖动到按钮身上,线才显示。2在手指在拖动的过程中,边拖动边显示 线。巧:2是对1的补充
      }
    
      //添加一根线到当前手指所在的点
      //[path addLineToPoint:self.curP];//2     touchesMoved方法中保存(self.curP = curP;)了手指在移动过程中的所有点,并利用 [self setNeedsDisplay]方法,实际是底层调用了drawRect:方法。所以在2中可以拿到手指在移动过程中的所有点,最后一个点肯定是当前的点,倒数第二个点相对于倒数第三个点来说,就是当前的点,以此类推,利用[path addLineToPoint:self.curP];将线添加到当前手指所在的点(手指在拖动的过程中,实际是有很多点,再加上后面的点相对于前面的点来说始终是当前的点,所以最终造成了边拖动边有线的结果)
    
      //设置线的状态
      [path setLineWidth:10];
      // 红色路径
      [[UIColor redColor] set];
      [path setLineJoinStyle:kCGLineJoinRound];
      // 描边路径
      [path stroke];
    

    }

}

  • (void)layoutSubviews {
    [super layoutSubviews];

    CGFloat x = 0;
    CGFloat y = 0;
    CGFloat btnWH = 74;

    int column = 3;
    CGFloat margin = (self.bounds.size.width - column * btnWH) / (column + 1);

    int curColumn = 0;
    int curRow = 0;

    //取出每一个子控件,设置frame. self.subviews.count值为9
    for (int i = 0 ; i < self.subviews.count; i++) {
    // 九宫格布局。获取到的列和行本质是坐标(0,0)(0,1)(0,2)(1,0)(1,1)(1,2)(2,0)(2,1)(2,1)

      //当前所在的列
      curColumn = i % column;
      //当前所在的行
      curRow = i / column;
    
      x = margin + (margin + btnWH) * curColumn;
      y = margin + (margin + btnWH) * curRow;
    
      //取出每一按钮
      UIButton *btn = self.subviews[i];
      // 确定每个按钮的frame
      btn.frame = CGRectMake(x, y, btnWH, btnWH);
    

    }

}
@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

  • (void)viewDidLoad {
    [super viewDidLoad];

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

  • (IBAction)clear:(id)sender {
    [self.drawView clear];
    }
    //撤销
  • (IBAction)undo:(id)sender {
    [self.drawView undo];
    }

//橡皮擦

  • (IBAction)erase:(id)sender {
    [self.drawView erase];
    }
    //选择照片

  • (IBAction)photo:(id)sender {

    UIImagePickerController *pickVC = [[UIImagePickerController alloc] init];
    //设置照片来源
    pickVC.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;

    //设置代理
    pickVC.delegate = self;

    [self presentViewController:pickVC animated:YES completion:nil];

}

//#pa - mark UIImagePickerControllerDelegate

  • (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {

    NSLog(@"%@",info);
    UIImage *image = info[UIImagePickerControllerOriginalImage];
    // NSData *data = UIImagePNGRepresentation(image);
    // [data writeToFile:@"/Users/xiaomage/Desktop/image.png" atomically:YES];
    //
    self.drawView.image = image;

    [self dismissViewControllerAnimated:YES completion:nil];
    }

//保存

  • (IBAction)save:(id)sender {

    //对画板作截屏
    //1.开启一个位图上下文
    UIGraphicsBeginImageContext(self.drawView.bounds.size);
    //2.把画板的内容渲染到上下文当中.
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    [self.drawView.layer renderInContext:ctx];
    //3.从上下文当中取出一张图片
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    //4.关闭上下文
    UIGraphicsEndImageContext();
    //5.把生成的图片写入到系统相册当中
    //注意:写放完成时调用的方法必须得是didFinishSavingWithError;
    UIImageWriteToSavedPhotosAlbum(newImage, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);

}

//当写入完成时调用

  • (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
    NSLog(@"%s",func);
    }

  • (void)success {

}
//设置线宽度

  • (IBAction)setLineWith:(UISlider *)sender {

    [self.drawView setLineWidth:sender.value];

}
//设置线的颜色

  • (IBAction)setLineColor:(UIButton *)sender {
    [self.drawView setLineColor:sender.backgroundColor];
    }

  • (BOOL)prefersStatusBarHidden {
    return YES;
    }

@end
DrawView.h文件

import <UIKit/UIKit.h>

@interface DrawView : UIView

//清屏

  • (void)clear;
    //撤销
  • (void)undo;
    //橡皮擦
  • (void)erase;
    //设置线宽度
  • (void)setLineWidth:(CGFloat)width;
    //设置线的颜色
  • (void)setLineColor:(UIColor *)color;

@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

  • (void)setImage:(UIImage *)image {
    _image = image;
    [self.pathArray addObject:image];
    //重绘
    [self setNeedsDisplay];
    }

//清屏

  • (void)clear {
    //清空所有的路径
    [self.pathArray removeAllObjects];
    //重绘
    [self setNeedsDisplay];
    }
    //撤销

  • (void)undo {
    //删除最后一个路径
    [self.pathArray removeLastObject];
    //重绘
    [self setNeedsDisplay];
    }
    //橡皮擦

  • (void)erase {

    [self setLineColor:[UIColor whiteColor]];
    }

//设置线宽度

  • (void)setLineWidth:(CGFloat)width {

    self.width = width;
    }
    ////设置线的颜色

  • (void)setLineColor:(UIColor *)color {
    self.color = color;
    }

  • (NSMutableArray *)pathArray {

    if (_pathArray == nil) {
    _pathArray = [NSMutableArray array];
    }
    return _pathArray;
    }

  • (void)awakeFromNib {

    //添加手势
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    [self addGestureRecognizer:pan];

    self.width = 1;
    // 将黑色存储到color属性中
    self.color = [UIColor blackColor];
    }

  • (void)pan:(UIPanGestureRecognizer *)pan {

    //画线
    //获取当前手指的点
    CGPoint curP = [pan locationInView:self];
    if (pan.state == UIGestureRecognizerStateBegan) {
    //创建路径
    //如果发现系统的类型没有办法瞒足要求时,自定义类.继承原来的类,在原来类的基础上,添加属于自己的东西.
    MyBezierPath *path = [[MyBezierPath alloc] init];
    [path setLineWidth:self.width];
    [path setLineJoinStyle:kCGLineJoinRound];
    [path setLineCapStyle:kCGLineCapRound];
    // 取出等号右边color存储的颜色赋值给左边的color(外界如果设置了颜色,那么会覆盖掉黑色)
    path.color = self.color;
    // 将path作为全局变量,供别的方法访问
    self.path = path;
    //path这个对象是个空的,应该放到第二个if里面吧。
    [self.pathArray addObject:path];
    [path moveToPoint:curP];
    } else if (pan.state == UIGestureRecognizerStateChanged) {
    //画的应该为直线,为什么是任意的线(也能画曲线)?你这句话是错误的。因为你给DrawView添加了手势,所以画的每一个点都会被记录到,两点之间确实是直线,但是如果任意两个相邻的点的距离都是1像素的话,每条线稍微弯折一点点,那么1000个点肯定能形成平滑的曲线
    [self.path addLineToPoint:curP];
    //将手势+贝塞尔曲线得到的每一个点绘制成路径(本质调用drawRect:)
    [self setNeedsDisplay];
    }

}
// 绘制路径

  • (void)drawRect:(CGRect)rect {

    //绘制出保存的所有路径
    for (MyBezierPath *path in self.pathArray) {

      if ([path isKindOfClass:[UIImage class]]) {
          UIImage *image = (UIImage *)path;
          // 在图片中绘制路径
          [image drawInRect:rect];
      }else {
          [path.color set];
          [path stroke];
      }
    

    }

}

@end
MyBezierPath.h文件

import <UIKit/UIKit.h>

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

@end

IOS那些事
Web note ad 1