什么是Quartz2D
Quartz2D 是一个二维绘图引擎
- 绘制图形:线条、三角形、矩形、圆形、弧等
- 绘制文字
- 绘制生成图片(图像)
- 读取、生成PDF
- 截取、裁剪图片
- 自定义UI控件
- 裁剪图片
- 涂鸦、画板
- 手势解锁
- 报表:折线图、饼状图、柱状图(github 搜 iOS chat)
Quartz2D在iOS开发中的价值
有些UI界面及其复杂、而且比较个性化,用普通的UI控件无法实现,这时可以利用Quartz2D技术奖控件内部的结构画出来,自定义控件的样子
其实,iOS中大部分控件的内容都是通过Quartz2D画出来的
因此,Quartz2D在iOS开发中最重要的一个价值是:自定义UI控件
- 图形上下文
- 图形上下文( Graphics Context):是一个 CGContextref类型的数据
- 图形上下文的作用
- 保存绘图信息、绘图状态
- 决定绘制的输出目标(绘制到什么地方去?)
(输出目标可以是PDF文件、 Bitmap或者显示器的窗口上)
绘制好的图片 (保存)-> 图形上下文 (显示)-> 输出目标
相同的一套绘图序列,指定不同的 Graphics Context,就可将相同的图像绘制到不同的目标 上
Quartz2D提供了以下几种类型的 Graphics Context
- Bitmap Graphics
- Context PDF Graphics Context
- Window Graphics Context
- Layer Graphics Context
自定义控件
- 如何利用 Quartz.2D自定义view?(自定义UI控件)
- 如何利用 Quart2D绘制东西到view上?
- 首先,得有图形上下文,因为它能保存绘图信息,并且决定着绘制到什么地方去
- 其次,那个图形上下文必须跟view相关联,オ能将内容绘制到view上面
自定义view的步骤
- 新建一个类,继承自 Uiview
- 实现-(void)drawRect:( CGRect)rect方法(实现这个方法会出现内存问题,例如画板里面,出现内存问题),(已经默认实现了图形上下文直接获取)然后在这个方法中
取得跟当前view相关联的图形上下文
- 把上下文渲染到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