第二十五 Quartz2D

  • drawRect:方法的使用

  • 常见图形的绘制:线条、多边形、圆

  • 绘图状态的设置:文字颜色、线宽等

  • 图形上下文状态的保存与恢复(图形上下文栈)

  • 图片裁剪

  • 截图

Quartz 2D是一个二维绘图引擎,同时支持iOS和Mac系统

  • Quartz 2D能完成的工作
    • 绘制图形 : 线条\三角形\矩形\圆\弧等
    • 绘制文字
    • 绘制\生成图片(图像)
    • 读取\生成PDF
    • 截图\裁剪图片
    • 自定义UI控件

裁剪图片

裁剪图片

涂鸦\画板

涂鸦\画板

手势解锁

手势解锁

报表:折线图\饼状图\柱状图

报表:折线图\饼状图\柱状图

Quartz2D在iOS开发中的价值

  • 为了便于搭建美观的UI界面,iOS提供了UIKit框架,里面有各种各样的UI控件

    • UILabel:显示文字
    • UIImageView:显示图片
    • UIButton:同时显示图片和文字(能点击)
    • … …
  • 利用UIKit框架提供的控件,拼拼凑凑,能搭建和现实一些简单、常见的UI界面

  • 但是,有些UI界面极其复杂、而且比较个性化,用普通的UI控件无法实现,这时可以利用Quartz2D技术将控件内部的结构画出来,自定义控件的样子

  • 其实,iOS中大部分控件的内容都是通过Quartz2D画出来的

  • 因此,Quartz2D在iOS开发中很重要的一个价值是:自定义view(自定义UI控件)

图形上下文

图形上下文(Graphics Context):是一个CGContextRef类型的数据

  • 图形上下文的作用
    • 保存绘图信息、绘图状态
    • 决定绘制的输出目标(绘制到什么地方去?)
    • (输出目标可以是PDF文件、Bitmap或者显示器的窗口上)

图形上下文

  • 相同的一套绘图序列,指定不同的Graphics Context,就可将相同的图像绘制到不同的目标上

图形上下文类别

自定义view

如何利用Quartz2D自定义view?(自定义UI控件)

如何利用Quartz2D绘制东西到view上?

  • 首先,得有图形上下文,因为它能保存绘图信息,并且决定着绘制到什么地方去
  • 其次,那个图形上下文必须跟view相关联,才能将内容绘制到view上面

  • 自定义view的步骤

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

单条线绘制


/**
 *  绘图的步骤
 *  1.获取图形上下文
 *  2.创建路径
 *  3.把路径添加到上下文中
 *  4.渲染上下文
 *
 *
 *  只有在这个方法中才能获取到跟view相关的layer的上下文
 *  当这个View要显示的时候才会调用drawRect绘制图形
 *  rect即使这个view的bounds
 */
- (void)drawRect:(CGRect)rect {

    [self drawLine];

}

- (void)drawLine {
    // 1.获取图形上下文
    // 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);
}
// 直接封装下载
- (void)drawLine1 {
    // 获取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // 描述路径
    // 设置起点
    // 这个方法底层调用了CGMutablePathRef
    CGContextMoveToPoint(ctx, 20, 20);

    // 设置终点
    CGContextAddLineToPoint(ctx, 100, 20);

    // 渲染
    CGContextStrokePath(ctx);
}

// UIBezierPath是没有办法画曲线
- (void)drawLine2 {
    // UIKit 已经封装好了一套方法用来绘制图形

    // 贝瑟尔路径

    // 创建路径
    UIBezierPath * path = [UIBezierPath bezierPath];

    // 设置起点
    [path moveToPoint:CGPointMake(10, 10)];

    // 添加路线
    [path addLineToPoint:CGPointMake(10, 100)];

    // 绘制
    [path stroke];
}

两条线绘制

- (void)drawLine3 {
    // 获取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // 设置起点
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, 10, 10);
    CGPathAddLineToPoint(path, NULL, 100, 10);
    CGPathAddLineToPoint(path, NULL, 100, 100);

    //添加
    CGContextAddPath(ctx, path);

    // 渲染
    CGContextStrokePath(ctx);
}
- (void)drawLine4 {
    // 获取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // 设置起点
    CGContextMoveToPoint(ctx, 10, 10);
    CGContextAddLineToPoint(ctx, 100, 200);
    CGContextAddLineToPoint(ctx, 50, 200);

    // 设置颜色之前一定要在渲染之前
    [[UIColor redColor] setStroke];

    // 设置线宽
    CGContextSetLineWidth(ctx, 10);

    // 设置连接样式
    CGContextSetLineJoin(ctx, kCGLineJoinRound);

    // 设置顶角样式
    CGContextSetLineCap(ctx, kCGLineCapRound);

    // 渲染
    CGContextStrokePath(ctx);
}
/**
 *  可以设置不同的颜色,使用底层封装的颜色没办法设置
 */
- (void)drawLine5 {
    UIBezierPath * path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(0, 0)];
    [path addLineToPoint:CGPointMake(100, 100)];
    path.lineWidth = 10;
    // 设置颜色
    [[UIColor redColor] setStroke];
    [path stroke];

    UIBezierPath * path2 = [UIBezierPath bezierPath];
    [path2 moveToPoint:CGPointMake(200, 20)];
    [path2 addLineToPoint:CGPointMake(100, 82)];
    path2.lineWidth = 3;
    // 设置颜色
    [[UIColor blueColor] setStroke];
    [path2 stroke];

}

绘制曲线只能用原生的,封装的只有一些常规的圆形,椭圆等

- (void)drawLine6 {
    // 如何绘制曲线

    // 原生绘制方法

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

    // 描述路径
    // 设置起点
    CGContextMoveToPoint(ctx, 50, 50);

    // cpx:控制点的x
    CGContextAddQuadCurveToPoint(ctx, 150, 20, 250, 50);


    // 渲染上下文
    CGContextStrokePath(ctx);

}

画图形

- (void)drawLine7 {
    // 画圆
    UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(20, 20, 150, 150) cornerRadius:75];
    [path stroke];

        // 填充必须是一个完整的路径
//    [path fill];
}
// 圆弧
// bezierPathWithArcCenter:中心
// radius:半径
// startAngle:起始角度
// endAngle:结束角度
// clockwise:是否是顺时针
    UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 100) radius:100 startAngle:0 endAngle:M_2_PI clockwise:YES];
    [path stroke];
   // 扇形
        UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 100) radius:100 startAngle:0 endAngle:M_PI_2 clockwise:YES];
    [path addLineToPoint:CGPointMake(100, 100)];

    // 填充系统会默认关闭路径
    [path fill];
   // 根据传入的值变化来绘制扇形
   - (void)setPress:(CGFloat)press {
    _press = press;

    [self setNeedsDisplay];
}

- (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 endAngle = -M_PI_2 + self.press * M_PI * 2;
    UIBezierPath * path = [UIBezierPath bezierPath];
    [path moveToPoint:center];
    [path addLineToPoint:CGPointMake(center.x, 10)];
    [path addArcWithCenter:center radius:radius startAngle:-M_PI_2 endAngle:endAngle clockwise:YES];
    [path fill];
}

绘制文字

    // 绘制文字
    NSString * str = @"结束角度:开始角度加上旋转角度结束角度:开始角度加上旋转角度结束角度:开始角度加上旋转角度";
    // 普通绘制
    // drawAtPoint:文字的开始点
    // withAttributes:文字的富文本属性,可以设置文字的大小,颜色,图文混排等

    // 不会自动换行 drawAtPoint
    [str drawAtPoint:CGPointZero withAttributes:nil];
    // 绘制文字
    NSString * str = @"结束角度:开始角度加上旋转角度结束角度:开始角度加上旋转角度结束角度:开始角度加上旋转角度";
    // 普通绘制
    // drawAtPoint:文字的开始点
    // withAttributes:文字的富文本属性,可以设置文字的大小,颜色,图文混排等
    NSMutableDictionary * dict = [NSMutableDictionary dictionary];
    // 设置文字大小
    dict[NSFontAttributeName] = [UIFont systemFontOfSize:30];
    dict[NSForegroundColorAttributeName] = [UIColor redColor];
    // 会自动换行 drawInRect
    [str drawInRect:self.bounds withAttributes:dict];

绘制图片

    // 绘制图片
    UIImage * image = [UIImage imageNamed:@"001"];
    // 默认绘制原图片尺寸
    // drawAtPoint:表示绘制起点
    [image drawAtPoint:CGPointZero];
    // 绘制图片
    UIImage * image = [UIImage imageNamed:@"001"];
    // 根据控件尺寸进行填充
    [image drawInRect:rect];
    // 绘制图片
    UIImage * image = [UIImage imageNamed:@"001"];
    // 根据原图片尺寸进行平铺
    [image drawAsPatternInRect:rect];

裁剪

 // 裁剪,保留设置的CGRectMake(0, 0, 50, 50)
 // 注意:裁剪必须要放在绘制图形之前
    UIRectClip(CGRectMake(0, 0, 50, 50));

    // 绘制图片
    UIImage * image = [UIImage imageNamed:@"001"];
    // 根据原图片尺寸进行平铺
    [image drawInRect:rect];

模仿雪花降落


#import "JXView.h"

static CGFloat offsetY = 0;
@implementation JXView

- (void)drawRect:(CGRect)rect {
    UIImage * image = [UIImage imageNamed:@"002"];

    [image drawAtPoint:CGPointMake(50, offsetY)];

    offsetY += 1;

    if (offsetY > self.bounds.size.height) {
        offsetY = 0;
    }
}

// 系统会调用这个方法来加载xib文件
- (void)awakeFromNib {
    // 在绘制图形的时候我们一般不用NSTimer这个方法,因为调度优先级比较低,并不会准时调用
    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(change) userInfo:nil repeats:YES];
}

- (void)change {
    [self setNeedsDisplay];
}
@end
//
//  JXView.m
//  JXTimer
//
//  Created by 王加祥 on 16/5/4.
//  Copyright © 2016年 Wangjiaxiang. All rights reserved.
//

#import "JXView.h"

static CGFloat offsetY = 0;
@implementation JXView

- (void)drawRect:(CGRect)rect {
    UIImage * image = [UIImage imageNamed:@"002"];

    [image drawAtPoint:CGPointMake(50, offsetY)];

    offsetY += 1;

    if (offsetY > self.bounds.size.height) {
        offsetY = 0;
    }
}

// 系统会调用这个方法来加载xib文件
- (void)awakeFromNib {
    // 在绘制图形的时候我们一般不用NSTimer这个方法,因为调度优先级比较低,并不会准时调用
//    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(change) userInfo:nil repeats:YES];

    // CADisplayLink:每次屏幕刷新的时候就会调用,屏幕一般一秒刷新60次
    CADisplayLink * link = [CADisplayLink displayLinkWithTarget:self selector:@selector(change)];
    // 添加到主运行循环
    [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}

- (void)change {
    // 注意:这个方法并不会马上调用drawRect,其实这个方法只是给当前控件添加刷新的标记,等下一次屏幕刷新的时候才会调用drawRect
    [self setNeedsDisplay];
}
@end

drawRect:

  • 为什么要实现drawRect:方法才能绘图到view上?

    • 因为在drawRect:方法中才能取得跟view相关联的图形上下文
  • drawRect:方法在什么时候被调用?

    • 当view第一次显示到屏幕上时(被加到UIWindow上显示出来)
    • 调用view的setNeedsDisplay或者setNeedsDisplayInRect:时

Quartz2D须知

  • Quartz2D的API是纯C语言的

  • Quartz2D的API来自于Core Graphics框架

  • 数据类型和函数基本都以CG作为前缀
    • CGContextRef
    • CGPathRef
    • CGContextStrokePath(ctx);
    • ……

drawRect:中取得的上下文

  • 在drawRect:方法中取得上下文后,就可以绘制东西到view上

  • 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)
    
  • Mode参数决定绘制的模式

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

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

    void CGContextFillPath(CGContextRef c)
    

提示:一般以CGContextDraw、CGContextStroke、CGContextFill开头的函数,都是用来绘制路径的

图形上下文栈的操作

  • 将当前的上下文copy一份,保存到栈顶(那个栈叫做”图形上下文栈”)

    void CGContextSaveGState(CGContextRef c)
    
  • 将栈顶的上下文出栈,替换掉当前的上下文

    void CGContextRestoreGState(CGContextRef c)
    

矩阵操作

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

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

    void CGContextRotateCTM(CGContextRef c, CGFloat angle)
    
  • 平移

    void CGContextTranslateCTM(CGContextRef c, CGFloat tx, CGFloat ty)
    

Quartz2D的内存管理

  • 使用含有“Create”或“Copy”的函数创建的对象,使用完后必须释放,否则将导致内存泄露

  • 使用不含有“Create”或“Copy”的函数获取的对象,则不需要释放

  • 如果retain了一个对象,不再使用时,需要将其release掉

  • 可以使用Quartz 2D的函数来指定retain和release一个对象。例如,如果创建了一个CGColorSpace对象,则使用函数CGColorSpaceRetain和CGColorSpaceRelease来retain和release对象。

  • 也可以使用Core Foundation的CFRetain和CFRelease。注意不能传递NULL值给这些函数

图片水印

实现方式:利用Quartz2D,将水印(文字、LOGO)画到图片的右下角

  • 核心代码

    1. 开启一个基于位图的图形上下文

      void     UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale)
      
    2. 从上下文中取得图片(UIImage)

      UIImage* UIGraphicsGetImageFromCurrentImageContext();
      
    3. 结束基于位图的图形上下文

      void     UIGraphicsEndImageContext();
      

    目前我们需要绘制图片到新的图片上,因此需要用到位图上下文

    怎么获取位图上下文,注意位图上下文的获取方式跟layer上下文不一样,位图上下文需要我们手动创建

    开启一个位图上下文,注意位图上下文跟view无关,所以不需要再drawRect

    这个方法是我们直接在viewController中实现的,并不需要我们重新自定义view控件,重写drawRect方法

      UIImage * image = [UIImage imageNamed:@"001"];
    
      // 0.获取上下文,之前的都是在view的drawRect方法中获取,(跟上view关联的上下文的layer上下文)
      // 目前我们需要绘制图片到新的图片上,因此需要用到位图上下文
    
      // 怎么获取位图上下文,注意位图上下文的获取方式跟layer上下文不一样,位图上下文需要我们手动创建
    
      // 开启一个位图上下文,注意位图上下文跟view无关,所以不需要再drawRect
    
      // size:位图上下文的尺寸(新图片尺寸)
      // opaque:不透明度,YES:不透明,NO:透明,通常我们一般都是设置透明的上下文
      // scale:通常不需要缩放上下文,取值0表示不缩放
      UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
    
      // 1.绘制原生图片
      [image drawAtPoint:CGPointZero];
    
      // 2.给原生的图片添加文字
      NSString * str = @"rookieJX";
      NSMutableDictionary * dict = [NSMutableDictionary dictionary];
      dict[NSForegroundColorAttributeName] = [UIColor redColor];
      dict[NSFontAttributeName] = [UIFont boldSystemFontOfSize:20];
      [str drawAtPoint:CGPointMake(150, 270) withAttributes:dict];
    
      // 3.生成一张图片给我们,从上下文中获取图片
      UIImage * imageNow = UIGraphicsGetImageFromCurrentImageContext();
    
      // 4.关闭上下文
      UIGraphicsEndImageContext();
    
      self.imageView.image = imageNow;
    
      // 直接原始的绘图功能,也可以绘制,我们直接绘制图片
          UIImage * image = [UIImage imageNamed:@"001"];
    
      // 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, 100, 100)];
    
      [[UIColor redColor] set];
    
      [path stroke];
      //    // 3.生成一张图片给我们,从上下文中获取图片
      UIImage * imageNow = UIGraphicsGetImageFromCurrentImageContext();
    //
    //    // 4.关闭上下文
      UIGraphicsEndImageContext();
    //
      self.imageView.image = imageNow;
    
       UIImage * image = [UIImage imageNamed:@"001"];
    
      // size:位图上下文的尺寸(新图片尺寸)
      // opaque:不透明度,YES:不透明,NO:透明,通常我们一般都是设置透明的上下文
      // scale:通常不需要缩放上下文,取值0表示不缩放
      UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
    
      // 获取当前的上下文,不仅仅是layer的上下文,在这里是位图上下文
      CGContextRef ctf = UIGraphicsGetCurrentContext();
    
      CGContextMoveToPoint(ctf, 0, 0);
      CGContextAddLineToPoint(ctf, 200, 300);
    
      CGContextSetLineWidth(ctf, 20);
      [[UIColor redColor] setStroke];
    
      CGContextStrokePath(ctf);
         UIImage * imageNow = UIGraphicsGetImageFromCurrentImageContext();
    //
    //    // 4.关闭上下文
      UIGraphicsEndImageContext();
    //
      self.imageView.image = imageNow;
    

图片裁剪

  • 核心代码

    void CGContextClip(CGContextRef c)
    

    将当前上下所绘制的路径裁剪出来(超出这个裁剪区域的都不能显示)

    // 获取图片
      UIImage * image = [UIImage imageNamed:@"APPLE7"];
    
      // 开启位图上下文
      UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
    
      // 设置切割区域,正切图片
      // 创建图形路径
      UIBezierPath * path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
      // 把路径设置为裁剪区域
      [path addClip];
    
      // 绘制图片
      [image drawAtPoint:CGPointZero];
    
      // 从上下文中获取裁剪后的图片
      UIImage * iamgeNow = UIGraphicsGetImageFromCurrentImageContext();
    
      // 获取裁剪后的图片之后关闭上下文
      UIGraphicsEndImageContext();
    
      self.imageView.image = iamgeNow;
    

屏幕截图

  • 核心代码

    -(void)renderInContext:(CGContextRef)ctx;
    

    调用某个view的layer的renderInContext:方法即可

      // 获取位图上下文
      UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);
    
      // 获取上下文
      CGContextRef ctf = UIGraphicsGetCurrentContext();
    
      // 把图片的上下文渲染到layer上
      [self.view.layer renderInContext:ctf];
    
      // 获取到图片
      UIImage * imageNow = UIGraphicsGetImageFromCurrentImageContext();
    
      // 关闭图片上下文
      UIGraphicsEndImageContext();
    
      self.imageView.hidden = NO;
      // 将图片展示到控件上
      self.imageView.image = imageNow;
    
          // 将图片转换成二进制保存
      NSData * data = UIImagePNGRepresentation(imageNow);
      [data writeToFile:@"/Users/yuezuo/Desktop/image.png" atomically:YES];
    

    可以将截图代码封装为UIImage的扩展

    
     +(UIImage *)imageWithCapeture:(UIView *)view {
      // 获取位图上下文
      UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);
    
      // 获取上下文
      CGContextRef ctf = UIGraphicsGetCurrentContext();
    
      // 把图片的上下文渲染到layer上
      [view.layer renderInContext:ctf];
    
      // 获取到图片
      UIImage * imageNow = UIGraphicsGetImageFromCurrentImageContext();
    
      // 关闭图片上下文
      UIGraphicsEndImageContext();
    
      retutn imageNow;
    
    }
    

裁剪图片

 #import "ViewController.h"

@interface ViewController ()
/** 创建view */
@property (nonatomic,weak) UIView * cutterView;
/** 开始值 */
@property (nonatomic,assign) CGPoint startP;
@property (weak, nonatomic) IBOutlet UIImageView *iamgeView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    UIPanGestureRecognizer * pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    [self.view addGestureRecognizer:pan];
}

- (void)pan:(UIPanGestureRecognizer *)gesture {
    CGPoint currentP ;
    if (gesture.state == UIGestureRecognizerStateBegan) {// 当开始点击拖动的时候
        // 记录当前的view
        self.startP = [gesture locationInView:self.view];
    } else if (gesture.state == UIGestureRecognizerStateChanged) { // 当在拖拽中的时候
        // 记录拖动的时候的当前view
        currentP = [gesture locationInView:self.view];
        // 记录拖动的矩形
        CGFloat currentW = currentP.x - self.startP.x;
        CGFloat currentH = currentP.y - self.startP.y;
        // 当前的矩形
        self.cutterView.frame = CGRectMake(self.startP.x, self.startP.y, currentW, currentH);
    } else if (gesture.state == UIGestureRecognizerStateEnded) { // 当拖拽结束的时候开始截屏
        // 停止拖拽的时候隐藏view

        // 裁剪图片
        // 1. 获取图片位图上下文
        // 如果设置不透明设置为YES,那么超出区域的部分就会变成黑色,否则超出区域就会变成self.view的背景色
        UIGraphicsBeginImageContextWithOptions(self.iamgeView.bounds.size, NO, 0);

#warning 注意,这里要先设置裁剪区域之后才能进行渲染,因为在裁剪之后我们裁剪之外会变成白色,如果顺序颠倒之后的话就是将整个图形全部渲染到self.iamgeView.layer上
        // 2. 设置裁剪区域
        UIBezierPath * path = [UIBezierPath bezierPathWithRect:self.cutterView.frame];

        // 3. 裁剪
        [path addClip];

        // 1.1 获取上下文
        CGContextRef ctf = UIGraphicsGetCurrentContext();
        // 1.2 渲染上下文
        [self.iamgeView.layer renderInContext:ctf];

        // 4. 获取图片
        self.iamgeView.image = UIGraphicsGetImageFromCurrentImageContext();

        // 5. 关闭位图
        UIGraphicsEndImageContext();


        // 先将view从父控件中移除
        [self.cutterView removeFromSuperview];
        // 将其指针置空
        self.cutterView = nil;

    }

}

#pragma mark - 懒加载
- (UIView *)cutterView {
    if (_cutterView == nil) {
        // 因为_cutterView是一个弱引用,会一出生就直接死亡,需要一个强引用
        UIView * view = [[UIView alloc] init];
        view.backgroundColor = [UIColor blackColor];
        view.alpha = 0.4;
        // 当强引用走完这个方法之后也回死亡,只有加入到self.view中才能保证view一直存在
        [self.view addSubview:view];
        _cutterView = view;
    }
    return _cutterView;
}
@end

图片擦除

#import "ViewController.h"

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

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    UIPanGestureRecognizer * pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    [self.view addGestureRecognizer:pan];
}

- (void)pan:(UIPanGestureRecognizer *)gesture {

#warning 注意在这里我们擦除的时候不可以有约束,否则会在擦除图片的同时计算约束,会出现图片,我们只可以设置图片的frame
    // 获取当前点
    CGPoint currentP = [gesture locationInView:self.view];
    CGFloat clearW = 10;
    CGFloat clearH = clearW;
    CGFloat clearX = currentP.x - clearW * 0.5;
    CGFloat clearY = currentP.y - clearH * 0.5;
    CGRect rect = CGRectMake(clearX, clearY, clearW, clearH);
    // 获取上下文
    UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);

    // 渲染
    CGContextRef ctf = UIGraphicsGetCurrentContext();

    [self.imageViewTwo.layer renderInContext:ctf];

    // 设置裁剪区域
    CGContextClearRect(ctf, rect);

    self.imageViewTwo.image = UIGraphicsGetImageFromCurrentImageContext();

    // 关闭
    UIGraphicsEndImageContext();



}
@end

画板

目标效果


代码实现

画板目录


ViewController.h

#import <UIKit/UIKit.h>


@interface ViewController : UIViewController


@end

ViewController.m

#import "ViewController.h"
#import "JXDrawView.h"

@interface ViewController ()<UINavigationControllerDelegate,UIImagePickerControllerDelegate>

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

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];


}

// 清除
- (IBAction)clear:(UIBarButtonItem *)sender {
    [self.drawView clear];
}
// 撤销
- (IBAction)revoke:(UIBarButtonItem *)sender {
    [self.drawView revokePath];
}

// 擦除
- (IBAction)eraser:(UIBarButtonItem *)sender {
    self.drawView.lineColor = self.drawView.backgroundColor;
}

// 图片
- (IBAction)picture:(UIBarButtonItem *)sender {

    // 图片选择
    UIImagePickerController * picker = [[UIImagePickerController alloc] init];

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

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

}
// 保存
- (IBAction)save:(UIBarButtonItem *)sender {

    // 开启图片上下文
    UIGraphicsBeginImageContextWithOptions(self.drawView.bounds.size, NO, 0);

    // 开启上下文
    CGContextRef ctd = UIGraphicsGetCurrentContext();

    // 渲染
    [self.drawView.layer renderInContext:ctd];

    // 获取图片
    UIImage * image = UIGraphicsGetImageFromCurrentImageContext();

    // 关闭
    UIGraphicsEndImageContext();

    // 保存画板的内容放入相册
    // image:写入的图片
    // completionTarget图片保存监听者
    // 注意:以后写入相册方法中,想要监听图片有没有保存完成,保存完成的方法不能随意乱写
    UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);

}


- (IBAction)changeLineWidth:(UISlider *)sender {
    self.drawView.lineWidth = sender.value;
}
- (IBAction)changeColor:(UIButton *)sender {

    self.drawView.lineColor = sender.backgroundColor;
}

#pragma mark - UIImagePickerControllerDelegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {
    UIImage * image = info[UIImagePickerControllerOriginalImage];
    self.drawView.image = image;
    [self dismissViewControllerAnimated:YES completion:nil];
}
// 监听保存完成,必须实现这个方法
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
    NSLog(@"保存图片成功");
}
@end

JXDrawView.h

#import <UIKit/UIKit.h>

@interface JXDrawView : UIView

/** 线宽 */
@property (nonatomic,assign) CGFloat lineWidth;
/** 颜色 */
@property (nonatomic,strong) UIColor * lineColor;
/** 图片 */
@property (nonatomic,strong) UIImage * image;
// 撤销
- (void)revokePath;
// 删除
- (void)clear;

@end

JXDrawView.m

#import "JXDrawView.h"

#import "DrawPath.h"

@interface JXDrawView ()

/** 当前点位置 */
@property (nonatomic,assign) CGPoint curP;
/** 曲线 */
@property (nonatomic,strong) DrawPath * path;
/** 保存绘制曲线 */
@property (nonatomic,strong) NSMutableArray * pathArray;

@end

@implementation JXDrawView

- (void)awakeFromNib {
    [self setup];
}

- (instancetype)initWithFrame:(CGRect)frame {
    if (self == [super initWithFrame:frame]) {
        [self setup];
    }
    return self;
}


// 初始化
- (void)setup {
    UIPanGestureRecognizer * pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    [self addGestureRecognizer:pan];
    self.lineWidth = 1;
    self.lineColor = [UIColor blackColor];
}

- (void)pan:(UIPanGestureRecognizer *)gesture {
    // 取出当前位置
    self.curP = [gesture locationInView:self];

    // 当开始拖拽的时候
    if (gesture.state == UIGestureRecognizerStateBegan) {
        // 创建路径
        _path = [[DrawPath alloc] init];

        [self.path moveToPoint:self.curP];

        // 设置线宽
        _path.lineWidth = self.lineWidth;

        // 设置颜色
        _path.pathColor = self.lineColor;

        // 在创建之后就加入数组,当重绘的时候数组中就会有东西了
        [self.pathArray addObject:self.path];
    }

    // 添加路径
    [self.path addLineToPoint:self.curP];
    // 重绘
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect {

    if (self.pathArray.count == 0) return;
    for (DrawPath * path in self.pathArray) {
        if ([path isKindOfClass:[UIImage class]]) { // 为图片的时候

            UIImage * image = (UIImage *)path;
            [image drawInRect:rect];

        } else { // 为线的时候
            [path.pathColor set];
            [path stroke];
        }
    }
}

#pragma mark - 懒加载


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

#pragma mark - 画板操作
- (void)revokePath {
    [self.pathArray removeLastObject];
    [self setNeedsDisplay];
}
- (void)clear {
    [self.pathArray removeAllObjects];
    [self setNeedsDisplay];
}

- (void)setImage:(UIImage *)image {
    _image = image;
    [self.pathArray addObject:image];
    [self setNeedsDisplay];
}
@end

DrawPath.h

#import <UIKit/UIKit.h>

@interface DrawPath : UIBezierPath
/** 颜色 */
@property (nonatomic,strong) UIColor * pathColor;
@end

DrawPath.m

#import "DrawPath.h"

@implementation DrawPath

@end

将color转为UIImage

//将color转为UIImage
- (UIImage *)createImageWithColor:(UIColor *)color
{
    CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
    UIGraphicsBeginImageContext(rect.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [color CGColor]);
    CGContextFillRect(context, rect);
    UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return theImage;
}

results matching ""

    No results matching ""