QuartzCore(绘图)

什么是Quartz2D

Quartz2D 是一个二维绘图引擎

  1. 绘制图形:线条、三角形、矩形、圆形、弧等
  2. 绘制文字
  3. 绘制生成图片(图像)
  4. 读取、生成PDF
  5. 截取、裁剪图片
  6. 自定义UI控件
  7. 裁剪图片
  8. 涂鸦、画板
  9. 手势解锁
  10. 报表:折线图、饼状图、柱状图(github 搜 iOS chat)

Quartz2D在iOS开发中的价值

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

其实,iOS中大部分控件的内容都是通过Quartz2D画出来的
因此,Quartz2D在iOS开发中最重要的一个价值是:自定义UI控件

  • 图形上下文
  • 图形上下文( Graphics Context):是一个 CGContextref类型的数据
  • 图形上下文的作用
  1. 保存绘图信息、绘图状态
  2. 决定绘制的输出目标(绘制到什么地方去?)
    (输出目标可以是PDF文件、 Bitmap或者显示器的窗口上)

绘制好的图片 (保存)-> 图形上下文 (显示)-> 输出目标

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

  • Quartz2D提供了以下几种类型的 Graphics Context

  1. Bitmap Graphics
  2. Context PDF Graphics Context
  3. Window Graphics Context
  4. Layer Graphics Context

自定义控件

  • 如何利用 Quartz.2D自定义view?(自定义UI控件)
  • 如何利用 Quart2D绘制东西到view上?
  1. 首先,得有图形上下文,因为它能保存绘图信息,并且决定着绘制到什么地方去
  2. 其次,那个图形上下文必须跟view相关联,オ能将内容绘制到view上面

自定义view的步骤

  1. 新建一个类,继承自 Uiview
  2. 实现-(void)drawRect:( CGRect)rect方法(实现这个方法会出现内存问题,例如画板里面,出现内存问题),(已经默认实现了图形上下文直接获取)然后在这个方法中取得跟当前view相关联的图形上下文
  3. 把上下文渲染到layer层

上下文状态栈


  CGContextSaveGState(ctx)
     1. 把当前上下文的状态保存到上下文状态栈,相当于copy一份状态,状态是设置的
属性比如颜色,宽度等。 
    
  CGContextRestoreGState(ctx)
    1. 从当前的上下文状态栈中取出栈顶的状态,覆盖掉当前的上下文状态(取出状
            态栈顶部的状态属性,覆盖当前上下文的状态)
   
  CGContextStrokePath(ctx)
    1. 取出上下文当中所有绘制的路径
    2. 把上下文当中的状态应用到所有路径当中,

UIBeziePath

@implementation XLShopBeziePath

#pragma mark - life cycle

- (instancetype)init {
    if (self = [super init]) {
//        + (instancetype)bezierPath;
        // 画矩形
//        + (instancetype)bezierPathWithRect:(CGRect)rect;
        // 画椭圆
//        + (instancetype)bezierPathWithOvalInRect:(CGRect)rect;
        // 画矩形带有圆角
//        + (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius;
        
//        + (instancetype)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii;
        // 画弧
        // center:弧所在的圆心
        // radius: 弧所在的半径
        // startAngle:弧开始的角度,0度数,在圆的最右侧,向上度数是负数,向下度数是正的
        // endAngle:弧结束的角度。到哪个位置
        // clockwise: 是否为顺时针,怎么样到这个位置
//        + (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
//        + (instancetype)bezierPathWithCGPath:(CGPathRef)CGPath;
    }
    return self;
}

@end
#import "DrawView.h"

@implementation DrawView

#pragma mark - life cycle

//作用:专门用来绘图
//什么时候调用:当View显示时调用
//参数:当前View的 bounds
- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
   // [self drawCurveLine];
   // 其内部实现了1,2,3,4 步骤
  UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:rect];
    [[UIColor redColor] set];
    [path fill];
}

#pragma mark - private methods

- (void)drawLine {
    //在此方法内部会自动创建一个跟Vie相关联的上下文
    //可以直接获取
    //无论是开启上下文,还是获取上下文,都是以UIGraphics 
    //1,获取当前跟View相关联的上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    //2.描述路径
    // 一个路径可以描述多条线
    UIBezierPath *path = [UIBezierPath bezierPath];
     
    //2.1设置起点
    //坐标原点是以当前绘制View的左上角为(0,0) 
    [path moveToPoint:CGPointMake(50, 50)];

    //2.2添加一根线到某个点,画了一条线
    [path addLineToPoint:CGPointMake(150, 150)];
    // 在当前画板新开一个起点再次画一条线
    [path moveToPoint:CGPointMake(100, 50)];
    [path addLineToPoint:CGPointMake(150, 250)];
    
    // 把上一个路径的重点,作为下一个路径的起点
    [path addLineToPoint:CGPointMake(250, 50)];
    
    
    //设置上下文的状态 
    //设置线宽度
 
    CGContextSetLineWidth(ctx, 10);
    //设置上下文的连接样式
    CGContextSetLineJoin(ctx, kCGLineJoinRound);
    //设置顶角样式
    CGContextSetLineCap(ctx, kCGLineCapRound);
    
    // 设置线条颜色
    // 方式一
    CGContextSetRGBStrokeColor(ctx, 1, 1, 0, 1);
    // 方式二
    //设置线的颜色
    //setStroke setFill
    //如果直接使用set,会自动匹配渲染的模式
    //[[UIColor redColor]  set];

    
    
     //把路径添加到上下文
    // 3,把路径添加到上下文
    CGContextAddPath(ctx, path.CGPath);
    //4.把上下文的当中绘制的所有路径渲染View相关联的ayer当中, 
    //渲染的方式有两种:
    //描边: stroke 
    //填充:fi11
    CGContextStrokePath(ctx);
}
- (void)drawCurveLine {
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    UIBezierPath *path = [UIBezierPath bezierPath];
    // 1. 画曲线
    [path moveToPoint:CGPointMake(50, 150)];  
     //2. 添加一根曲线到某个点,添加一个控制点
    [path addQuadCurveToPoint:CGPointMake(250, 150) controlPoint:CGPointMake(150, 50)];
     //3. 把路径添加到上下文
    CGContextAddPath(ctx, path.CGPath);
     //4. 把上下文的内容渲染到View的layer;
    CGContextStrokePath(ctx);
}

@end

扇形进度条

@implementation ProgressView

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor darkGrayColor];        
    }
    return self;
}

- (void)setPregress:(CGFloat)pregress {
    _pregress = pregress;
    // 通知系统重新绘制调用drawRect
    [self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    // 原点
    CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
    // 半径
    CGFloat radius = rect.size.width * 0.5 - 10;
    // 开始弧度
    CGFloat start = -M_PI_2;
    // 结束弧度,开始的弧度加上360弧度回到开始的位置
    CGFloat end = start + M_PI * self.pregress * 2;
    // 获取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    // 曲线
    UIBezierPath *bezierPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:start endAngle:end clockwise:YES];
    
    [bezierPath addLineToPoint:center];
    // 添加到上下文
    CGContextAddPath(ctx, bezierPath.CGPath);
    
    [[UIColor colorWithRed:1 green:0 blue:1 alpha:0.8] set];
    //绘制路径
    // 线条路径
//    CGContextStrokePath(ctx);
    // 填充路径
    CGContextFillPath(ctx);
}

- (UIColor *)randColor  {
    CGFloat r = arc4random_uniform(256);
    CGFloat g = arc4random_uniform(256);
    CGFloat b = arc4random_uniform(256);
    return [UIColor colorWithRed:r green:g blue:b alpha:1];
}
@end

CADisplayLink的使用

#import "XLShopDrawAnimationImage.h"
static CGFloat showY = 0;
@interface XLShopDrawAnimationImage ()
@property (nonatomic, strong) CADisplayLink *displayLink;
@end
@implementation XLShopDrawAnimationImage

#pragma mark - life cycle
- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
    }
    return self;
}

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    UIImage *image = [UIImage imageNamed:@"雪花"];
    [image drawAtPoint:CGPointMake(0, showY)];
}

#pragma mark - public methos
- (void)startDisPlayLink {
    self.displayLink.paused = NO;
}
- (void)pausedDisplayLink {
    self.displayLink.paused = YES;
}
- (void)invalidateDisplayLink {
    [self.displayLink invalidate];
    self.displayLink = nil;
}
#pragma mark - private methods

- (void)update {
    showY += 50;
    if (showY > self.frame.size.height) {
        showY = 0;
    }
    [self setNeedsDisplay];

}

#pragma mark - setters and getters

- (CADisplayLink *)displayLink {
    if (_displayLink == nil) {
        //为什么会很流畅?
        
        //当每一次屏幕刷新新时调用,(屏幕每一秒刷新60次)
        //  setNeedsDisplay会调用 drawRect
        //  井不是立马调用,是当屏幕刷新时才去调用 drawRect ,它跟CADisplayLink执行方法时间是相同的
        CADisplayLink *displayLink = [CADisplayLink  displayLinkWithTarget:self selector:@selector(update)];
        _displayLink = displayLink;   
        [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:( NSRunLoopCommonModes)];
    }
    return _displayLink;
}
@end

画文字

@implementation XLShopDrawText
#pragma mark - life cycle
- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    NSString *text = @"drawText";
    
    NSDictionary *atribute = 
    @{
        // 设置字体
        NSFontAttributeName:[UIFont boldSystemFontOfSize:20],
        // 设置颜色
        NSForegroundColorAttributeName:[UIColor redColor],
        // 设置描边
        NSStrokeColorAttributeName: [UIColor blueColor],
        NSStrokeWidthAttributeName: @3,
        // 设置阴影
        NSShadowAttributeName: ({
            NSShadow *shadow = [[NSShadow alloc] init];
            shadow.shadowColor = [UIColor greenColor];
            // 设置阴影的偏移量
            shadow.shadowOffset = CGSizeMake(10, 10);
            shadow;
        })
    };
    [text drawAtPoint:CGPointZero withAttributes:atribute];
    
}

@end

图片加水印

1. 位图上下文
要手动去创違一个位图上下文 创建位图上下文时,要指定大小
指定大小,决定着生成图片的尺寸是多大

2. 从上下文当中生成一张图片
把上下文当中绘制的所有内容合成一起生成张跟上下文尺寸 大小一样的图片

3. 手动创建的上下文,一定要手动去销毁掉
@implementation UIImage (Txt)

- (UIImage *)imageName:(NSString *)name txt:(NSString *)txt {
    if (name == nil || name.length == 0) {
        return nil;
    }
    UIImage *image = [UIImage imageNamed:name];
//    1. 位图上下文
//    要手动去创違一个位图上下文 创建位图上下文时,要指定大小
//    指定大小,决定着生成图片的尺寸是多大
    UIGraphicsBeginImageContext(image.size);
//    2. 从上下文当中生成一张图片
//    把上下文当中绘制的所有内容合成一起生成张跟上下文尺寸 大小一样的图片
    [image  drawAtPoint:CGPointZero];
    [txt drawAtPoint:CGPointMake(image.size.width / 2.f, image.size.height / 2) withAttributes:@{}];
    image = UIGraphicsGetImageFromCurrentImageContext();
//    3. 手动创建的上下文,一定要手动去销毁掉
    UIGraphicsEndImageContext();
    return image;
}

@end

裁剪图片

C语言的API,rect是point,需要转换成像素
手动转换成像素 = 图片size * [UIScreen mainScreen].scale

裁剪图片的区域

CGImageCreateWithImageInRect(<#CGImageRef  _Nullable image#>, <#CGRect rect#>)

OC语言裁剪size内部会转成像素size

- (UIImage *)imageClicpedWithImageName:(NSString *)name {
    if (name == nil || name.length == 0) {
        return nil;
    }
    //1,加图片
    UIImage* image = [UIImage imageNamed:name];
    //2.开启一个位图上下文 
    UIGraphicsBeginImageContext(image.size);
    //3.设置一个圆形的裁剪区域
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, image.size.width, image.size.height)];

    //把路径设置为裁剪区域(超出裁剪区域以外的内容会被自动剪掉)
    [path addClip];//对后面绘制的内容有效果,已经绘制到上下文当中的内容,不会被裁剪 
    //4,把图片绘制到上下文当中
    [image drawAtPoint:CGPointZero];
    //5,从上下文当中生成一张图片
    image  = UIGraphicsGetImageFromCurrentImageContext();
    //6,关闭上下文
    UIGraphicsEndImageContext();
    return image;
}

带有边框的裁剪

边框宽度
1.先开启一个图片上下文 尺寸大小在原始图片基础上宽高都加上两倍边框宽度
2.填充一个圆形路径.这个圆形路径大小,和上下文尺寸大小一样.
3.添加一个小圆,小圆,xy从边框宽度位置开始添加,宽高和原始图片一样大小.把小圆设为裁剪区域
4.把图片给绘制上去
+ (UIImage *)imageClicpedWithImageName:(NSString *)name {
 
    return [self imageClicpedWithImageName:name borderWidth:0];
}

+ (UIImage *)imageClicpedWithImageName:(NSString *)name borderWidth:(CGFloat)borderWith {
    if (name == nil || name.length == 0) {
        return nil;
    }
    //1,加图片
    UIImage* image = [UIImage imageNamed:name];
    //2.开启一个位图上下文 
    CGSize size = CGSizeMake(image.size.width + borderWith * 2, image.size.width + borderWith * 2);
    UIGraphicsBeginImageContext(size);
    //3 尺寸大小在原始图片基础上宽高都加上两倍边框宽度
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, size.width, size.width)];
    [[UIColor purpleColor] set];
    [path fill];
    //4 添加一个小圆,小圆,xy从边框宽度位置开始添加,宽高和原始图片一样大小.把小圆设为裁剪区域
    UIBezierPath *imagePath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(borderWith, borderWith, size.width - 2 * borderWith, size.width - 2 * borderWith)];
    [imagePath addClip];
    //5.把图片给绘制上去
    [image drawAtPoint:CGPointMake(borderWith, borderWith)];
    //6. 关闭位图上下文
    image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
    
}

+(UIImage *)imageWithScreenCaptureView:(UIView *)view {
    if (view == nil || ![view isKindOfClass:[UIView class]]) {
        return nil;
    }
    // 生成一张图片
    // 1. 开启位图上下文
    UIGraphicsBeginImageContext(view.bounds.size);
    // 2. 获取当前的上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    // 3. 把控制器view的内容绘制到上下文当中,(上下文于layer之间是通过渲染方式进行交互)
    [view.layer renderInContext:ctx];
    // 4. 从上下文当中生成一张图片
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    
    // 5. 关闭上下文
    UIGraphicsEndImageContext();
    
    return image;
    
}


- (UIImage *)imageWithScreenCaptureView:(UIView *)view scale:(BOOL)scale isOpaque:(BOOL)opaque{
    //2.0当前加載的图片是@2X
    //3.0当前加载的图片是3X
    //当前像素坐标与点坐标的比例
    //在0C像素坐标会自动根据比例转成点坐标
    //在C语言当中是不会转换
    //[UIScreen mainScreen].scale
    
    if (view == nil || ![view isKindOfClass:[UIView class]]) {
        return nil;
    }
    // 生成一张图片
    // 1. 开启位图上下文
    // 参数: 
    //  opaque(不透明)
    //  scale == 0 (系统内部自动 * [UIScreen mainScreen].scale),生成图片的大小 = viewSize * scale。
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, opaque, scale);
    // 2. 获取当前的上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    // 3. 把控制器view的内容绘制到上下文当中,(上下文于layer之间是通过渲染方式进行交互)
    [view.layer renderInContext:ctx];
    // 4. 从上下文当中生成一张图片
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        // 5. 关闭上下文
    UIGraphicsEndImageContext();
    return image;
}

图片遮罩裁剪

@interface XLShopCoverImageView ()
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UIView *coverView;
@property (nonatomic, assign) CGFloat beginX;
@property (nonatomic, assign) CGFloat beginY;
@end
@implementation XLShopCoverImageView

#pragma mark - life cycle
- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        self.clipsToBounds = YES;
        [self addSubview:self.imageView];
    }
    return self;
}
- (void)layoutSubviews {
    [super layoutSubviews];
    self.imageView.frame = self.bounds;
}

#pragma mark event response

- (void)panGes:(UIPanGestureRecognizer *)panGes {
    CGPoint point  = [panGes locationInView:self.imageView];
    if (panGes.state == UIGestureRecognizerStateBegan) {
        self.beginX = point.x;
        self.beginY = point.y;
    } else if (panGes.state == UIGestureRecognizerStateChanged) {
        CGFloat coverX = self.beginX;
        CGFloat coverY = self.beginY;
        CGFloat coverViewWidth = point.x - self.beginX;
        CGFloat coverViewHeight = point.y - self.beginY;
        self.coverView.frame = CGRectMake(coverX, coverY, coverViewWidth, coverViewHeight);
    } else if (panGes.state == UIGestureRecognizerStateEnded) {
        // 1. 开启位图上下文
        UIGraphicsBeginImageContextWithOptions(self.imageView.frame.size, NO, 0);
        // 2. 设置裁剪区域
        UIRectClip(self.coverView.frame);
        // 3. 将图片渲染到上下文中
        // 3.1 获取刚才创建的上下文
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        // 3.2 通过layer把imageView内容层渲染给上下文
        [self.imageView.layer renderInContext:ctx];
        // 4. 获取裁剪后的图片
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        // 5. 把上下文关闭
        UIGraphicsEndImageContext();
        [self.coverView removeFromSuperview];
        
        // 把刚才裁剪的图片显示在imageView上
        self.imageView.image = image;
    }
}

#pragma mark - public methods
- (void)setImageName:(NSString *)imageName {
    _imageName = imageName;
    self.imageView.image = [UIImage imageNamed:imageName];
}

#pragma mark - getter and setters

- (UIImageView *)imageView {
    if (_imageView == nil) {
        _imageView = [[UIImageView alloc] init];
        _imageView.userInteractionEnabled = YES;
        UIPanGestureRecognizer *panGes = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGes:)];
        [_imageView addGestureRecognizer:panGes];
    }
    return _imageView;
}
- (UIView *)coverView {
    if (_coverView == nil) {
        UIView *view = [UIView new];
        view.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5];
        [self addSubview:view];
        _coverView = view;
    }
    return _coverView;
}

@end

图片擦除

- (void)panGes:(UIPanGestureRecognizer *)panGes {
    CGPoint point = [panGes locationInView:self.imageView];
    CGFloat with = 30, height = 30;
    CGFloat x = point.x - 10, y = point.y - 10;
    UIGraphicsBeginImageContextWithOptions(self.imageView.bounds.size, YES, 0.0);
    CGContextRef ctr = UIGraphicsGetCurrentContext();
    [self.imageView.layer renderInContext:ctr];
    
    CGContextClearRect(ctr, CGRectMake(x, y, with, height));
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    self.imageView.image = image;
    UIGraphicsEndImageContext();
}

判断一个点是否在某个按钮的身上

- (UIButton *)btnRectContainsPoint:(CGPoint)point {
    for (UIButton *btn in self.subviews) {
        if (CGRectContainsPoint(btn.frame, point)) {
            return btn;
        }
    }
    return nil;
}

自定义UIBezierPath

@interface XLShopBezierPath : UIBezierPath
@property (nonatomic, strong) UIColor *lineColor;
@end
@implementation XLShopBezierPath
#pragma mark - life cycle

- (instancetype)init {
    if (self = [super init]) {
//        + (instancetype)bezierPath;
        // 画矩形
//        + (instancetype)bezierPathWithRect:(CGRect)rect;
        // 画椭圆
//        + (instancetype)bezierPathWithOvalInRect:(CGRect)rect;
        // 画矩形带有圆角
//        + (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius;
        
//        + (instancetype)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii;
        // 画弧
        // center:弧所在的圆心
        // radius: 弧所在的半径
        // startAngle:弧开始的角度,0度数,在圆的最右侧,向上度数是负数,向下度数是正的
        // endAngle:弧结束的角度。到哪个位置
        // clockwise: 是否为顺时针,怎么样到这个位置
//        + (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
//        + (instancetype)bezierPathWithCGPath:(CGPathRef)CGPath;
    }
    return self;
}
@end

自定义可旋转捏合缩放拖动的XLShopHandleImageView

.h

@class XLShopHandleImageView;
@protocol XLShopHandleImageViewDelegate 

-(void)xlShopHandleImageView:(nullable XLShopHandleImageView *)view newImage:(nullable UIImage *)image;

@end
NS_ASSUME_NONNULL_BEGIN

@interface XLShopHandleImageView : UIView
@property (nonatomic, weak) iddeletate;
@property (nonatomic, strong) UIImage *image;
@end
NS_ASSUME_NONNULL_END

.m

@interface XLShopHandleImageView ()
@property (nonatomic, weak) UIImageView *imageView;
@end
@implementation XLShopHandleImageView

#pragma mark - life cycle

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor clearColor];
        self.clipsToBounds = YES;
    }
    return self;
}


#pragma mark - event response

- (void)rotationGes:(UIRotationGestureRecognizer *)rorationGes {
    // 获取旋转角度(已经是弧度)
    // 相对于最原始的弧度
    CGFloat roration =  rorationGes.rotation;
    rorationGes.view.transform = CGAffineTransformRotate(rorationGes.view.transform, roration);
    // 清零操作,每次旋转都是以弧度为0的基础上进行旋转
    [rorationGes setRotation:0];
}

- (void)pinGes:(UIPinchGestureRecognizer *)pin {
    // 放大缩小
    // 获取缩放比例相对于最原始的比例
    CGFloat scale = pin.scale;
    pin.view.transform = CGAffineTransformScale(pin.view.transform, scale, scale);
    // 置1操作,每一次操作都是在sacle= 1基础下进行缩放
    [pin setScale:1];
    
}

// 获取的偏移量是相对于最原始的点
- (void)panGes:(UIPanGestureRecognizer *)pan {
    CGPoint point = [pan translationInView:pan.view];
    pan.view.transform = CGAffineTransformTranslate(pan.view.transform, point.x, point.y);
    // 清零操作,每次都是以偏移量为0的基础上进行偏移
    [pan setTranslation:CGPointMake(0, 0) inView:pan.view];
}

- (void)longGes:(UILongPressGestureRecognizer *)longGes {
    if (longGes.state == UIGestureRecognizerStateBegan) {
        // 实现闪亮效果
        [UIView animateWithDuration:0.5 animations:^{
            self.imageView.alpha = 0; 
        } completion:^(BOOL finished) {
            self.imageView.alpha = 1;

            // 生成图片
            // 1.开启位图
            UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
            // 2.获取上下文
            CGContextRef ctr =  UIGraphicsGetCurrentContext();
            // 3.把self.layer层渲染到上下文,如果把imageView的layer层渲染到上下文,会导致imageView的形变没有改变,是通过原点(正方向)所在的坐标系渲染到上下文中
            [self.layer renderInContext:ctr];
            // 4.获取图片
            UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
            // 5.关闭上下文
            UIGraphicsEndImageContext();
            // 6. 通知代理生成图片
            if (self.deletate && [self.deletate respondsToSelector:@selector(xlShopHandleImageView:newImage:)]) {
                [self.deletate xlShopHandleImageView:self newImage:image];
            }
            // 移除操作
            [self removeFromSuperview];
        }];
    }
}
#pragma mark - UIGestureRecognizerDelegate
// 代理实现协议方法,通知手势可以让Target支持多个手势
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return YES;
}


#pragma mark - getters and setters

- (UIImageView *)imageView {
    if (_imageView == nil) {
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.bounds];
        imageView.userInteractionEnabled = YES;
        [self addSubview:imageView];
        // 手势默认是不能同时支持多个手势的,可以指定代理,让代理实现shouldRecognizeSimultaneouslyWithGestureRecognizer 返回YES,返回多个手势
       
        UIPanGestureRecognizer *panGes = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGes:)];
        [imageView addGestureRecognizer:panGes];
        // 相对于坐标系,坐标系有大小有方向
        //        - (CGPoint)translationInView:(nullable UIView *)view;
        // 转换后清零操作
        //        - (void)setTranslation:(CGPoint)translation inView:(nullable UIView *)view
        

        // 捏合(放大缩小)
        UIPinchGestureRecognizer *pinGes = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinGes:)];
        pinGes.delegate = self;
        [imageView addGestureRecognizer:pinGes];
        
        // 旋转
        UIRotationGestureRecognizer *rotationGes = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotationGes:)];
        rotationGes.delegate = self;
        [imageView addGestureRecognizer:rotationGes];
        
        // 长按
        UILongPressGestureRecognizer *longGes = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longGes:)];
        [imageView addGestureRecognizer:longGes];
        _imageView = imageView;
        
        
    }
    return _imageView;
}
#pragma mark - getters and setters

- (void)setImage:(UIImage *)image {
    _image = image;
    self.imageView.image = image;
}
@end

画板

.h

#import 
@class XLShopBezierPath,XLShopHandleImageView;

NS_ASSUME_NONNULL_BEGIN
@interface XLShopDrawingBoardView : UIView
@property (nonatomic, strong,readwrite,nullable) UIImage *backgroundImage;
@property (nonatomic, strong,readwrite,nullable) UIColor *lineColor;
@property (nonatomic, readwrite) CGFloat lineWidth;
- (void)setCanHandleFrontImage:(UIImage *)canHandleFrontImage;
- (void)imageWithScreenCaptureViewCallback:(void(^)(UIImage* image))callback;
- (void)imageSaveToPhotosAlbumCallback:(void(^)(UIImage *image, NSError *error))saveToAlbumCallback;
- (void)clear;
- (void)back;
- (void)front;
- (void)erase;

@end

.m

#import "XLShopDrawingBoardView.h"
#import "XLShopBezierPath.h"
#import "XLShopHandleImageView.h"

@interface XLShopDrawingBoardView ()
@property (nonatomic, weak) XLShopHandleImageView *handleImageView;
@property (nonatomic, copy) void(^saveToAlbumCallback)(UIImage *image, NSError *error);
@property (nonatomic, strong) NSMutableArray *pathArray;
@property (nonatomic, strong) NSMutableArray *pathPopedArray;
@property (nonatomic, strong) UIView *contentView;
@property (nonatomic, strong) XLShopBezierPath *bezierPath;
@end
@implementation XLShopDrawingBoardView

#pragma mark - life cycle
- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor whiteColor];
        [self addSubview:self.contentView];
        self.lineColor = [UIColor blackColor];
        self.lineWidth = 12;
    }
    return self;
}
- (void)drawRect:(CGRect)rect {
    for (XLShopBezierPath *path in self.pathArray) {
        // 颜色必须在drawRect方法绘制
        if ([path isKindOfClass:[UIImage class]]) {
            UIImage *image = (UIImage *)path;
            [image drawInRect:rect];
        } else {
            [path.lineColor set];
            [path stroke];
        }
    }
}
- (void)layoutSubviews {
    [super layoutSubviews];
    self.contentView.frame = self.bounds;
}

#pragma mark - event response
- (void)panGes:(UIPanGestureRecognizer *)panGes {
    CGPoint point = [panGes locationInView:self.contentView];
    if (!CGRectContainsPoint(panGes.view.frame, point)) {
        return;
    }
    if (panGes.state == UIGestureRecognizerStateBegan) {
        self.bezierPath = [XLShopBezierPath bezierPath];
        self.bezierPath.lineWidth = self.lineWidth;
        self.bezierPath.lineColor = self.lineColor;
        self.bezierPath.lineJoinStyle = kCGLineJoinRound;
        self.bezierPath.lineCapStyle = kCGLineCapRound;
        [self.pathArray addObject:self.bezierPath];
        [self.bezierPath moveToPoint:point];
    } else if (panGes.state == UIGestureRecognizerStateChanged) {
        [self.bezierPath addLineToPoint:point];
        [self setNeedsDisplay];
    }
}


#pragma mark - XLShopHandleImageViewDelegate
-(void)xlShopHandleImageView:(nullable XLShopHandleImageView *)view newImage:(nullable UIImage *)image {
    self.backgroundImage = image;
}

#pragma mark - public methods

- (void)clear {
    [self.pathArray removeAllObjects];
    [self.pathPopedArray removeAllObjects];
    [self setNeedsDisplay];
}
- (void)back {
    UIBezierPath *lastBezierPath =  self.pathArray.lastObject;
    if(lastBezierPath == nil) return;
    [self.pathPopedArray addObject:lastBezierPath];
    [self.pathArray removeObject:lastBezierPath];
    [self setNeedsDisplay];
}
- (void)front {
    UIBezierPath *lastBezierPath =  self.pathPopedArray.lastObject;
    if(lastBezierPath == nil) return;
    [self.pathArray addObject:lastBezierPath];
    [self.pathPopedArray removeObject:lastBezierPath];
    [self setNeedsDisplay];
}
- (void)erase {
    self.lineColor = [UIColor whiteColor];
}

- (void)imageWithScreenCaptureViewCallback:(void(^)(UIImage* image))callback {
    if(!callback) return;
    UIImage *image = [self screenCapturView];
    callback(image);
}

- (void)imageSaveToPhotosAlbumCallback:(void(^)(UIImage *image, NSError *error))saveToAlbumCallback {
    self.saveToAlbumCallback = saveToAlbumCallback;
    UIImage *image = [self screenCapturView];
    UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
}

#pragma mark - private methods 

- (UIImage *)screenCapturView {
    UIGraphicsBeginImageContextWithOptions(self.contentView.frame.size, NO, 0);
    CGContextRef ctr = UIGraphicsGetCurrentContext();
    [self.layer renderInContext:ctr];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

-(void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
    if (!self.saveToAlbumCallback) return;
    self.saveToAlbumCallback(image, error);
}

#pragma mark - getters and setters

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

- (NSMutableArray *)pathPopedArray {
    if (_pathPopedArray == nil) {
        _pathPopedArray = [NSMutableArray array];
    }
    return _pathPopedArray;
}

- (UIView *)contentView {
    if (_contentView == nil) {
        _contentView = [UIView new];
        _contentView.backgroundColor = [UIColor clearColor];
        UIPanGestureRecognizer *panGes = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGes:)];
        [_contentView addGestureRecognizer:panGes];
    }
    return _contentView;;
}

- (XLShopHandleImageView *)handleImageView {
    if (_handleImageView == nil) {
        XLShopHandleImageView *handleImageView = [[XLShopHandleImageView alloc] initWithFrame:self.contentView.bounds];
        handleImageView.deletate = self;
        [self.contentView addSubview:handleImageView];
        _handleImageView = handleImageView;
    }
    return _handleImageView;
}
- (void)setBackgroundImage:(UIImage *)backgroundImage {
    if (!backgroundImage || ![backgroundImage isKindOfClass:[UIImage class]]) return;
    _backgroundImage = backgroundImage;
    [self.pathArray addObject:backgroundImage];
    [self setNeedsDisplay];
}
- (void)setCanHandleFrontImage:(UIImage *)canHandleFrontImage {
    self.handleImageView.image = canHandleFrontImage;
}
@end

你可能感兴趣的:(QuartzCore(绘图))