目录
一、Quartz2D
1、什么是Quartz2D
2、Quartz2D的一些实例
二、UIBezierPath
1、什么是UIBezierPath
2、UIBezierPath的一些实例
一、Quartz2D
Quartz2D编程指南
1、什么是Quartz2D
为了便于搭建美观的UI,iOS提供了UIKit框架,里面有各种各样的控件,比如我们想显示文字就用UILabel、想显示图片就用UIImageView、想显示文字和图片就用UIButton等,利用这些控件拼拼凑凑就能搭建出一些常见的UI。
但是有些UI极其复杂、而且比较个性化,利用UIKit框架里的控件根本无法实现,这时我们就可以考虑用Quartz2D来自定义UIKit框架以外的控件——即直接在一个view上把我们想要显示的东西给绘制出来,你想绘制什么就能绘制什么。比如折线图控件本质上就是用Quartz2D在绘制直线、柱状图控件本质上就是用Quartz2D在绘制矩形、饼状图控件本质上就是用Quartz2D在绘制弧、转圈那种下载进度条控件本质上就是用Quartz2D在绘制圆、画板控件本质上就是用Quartz2D在绘制任意路径等。
所谓Quartz2D就是一个二维绘图引擎,同时支持iOS和macOS,它有很多能力,如:
- 绘制路径能力——直线、矩形、弧、圆、任意路径等
- 生成图片能力
- 绘制图片能力
- ...
实际开发中,我们主要就是用Quartz2D的这些能力来自定义UIKit框架以外的控件
1.1 Quartz2D的关键词一:图形上下文和路径
绘图需要画布和画笔,图形上下文就是Quartz2D的画布,路径就是Quartz2D的画笔。图形上下文中存储着我们绘制的每一条路径的信息(如每一条路径的起点和终点、每一条路径的颜色和宽度、每一条路径的首尾样式和转角样式),并且也决定着我们要把所有的路径渲染到什么输出设备上去显示(如输出设备可以是view.layer、Bitmap、PDF文件、显示器窗口或打印机,这个只要选不同类型的上下文就可以了)。
实际开发中,我们只需要自定义一个XXView继承自UIView,然后重写XXView的drawRect:
方法,在这个方法里通过UIGraphicsGetCurrentContext()
函数来获取XXView自带的图形上下文(自定义上下文会降低内存的使用效率,导致性能下降),然后通过路径来绘图就可以了。
1.2 Quartz2D的关键词二:内存管理
Quartz2D是一套C语言的API,不支持ARC,所以我们需要对它进行手动内存管理:
- 如果你创建或拷贝了一个对象,那么你将持有这个对象,因此你必须在不需要使用它的时候手动释放它。通常,如果使用了含有
Create
或Copy
单词的函数获取一个对象,当使用完后就必须手动释放它,否则将导致内存泄露;如果使用了不含有Create
或Copy
单词的函数获取一个对象,你将不会拥有对象的引用,不需要释放它; - 如果你想增加一个对象的引用计数,那么也必须retain它并且在不需要时release掉,可以使用Quartz2D的函数来指定retain和release一个对象。例如,如果创建了一个CGContextRef对象,则使用函数
CGContextRetain()
和CGContextRelease()
来retain和release对象。
2、Quartz2D的一些实例
2.1 绘制直线
-----------CustomView.m-----------
/// 作用:专门用来做绘图的
/// 调用时机:当前view即将显示到屏幕上的时候,系统会自动调用 || 如果我们想手动触发某个view的drawRect:方法,可以调用该view的setNeedsDisplay方法
/// @param rect 当前view的bounds,不是frame
- (void)drawRect:(CGRect)rect {
/*
1、获取图形上下文(默认的输出设备就是view.layer)
*/
CGContextRef context = UIGraphicsGetCurrentContext();
/*
2.1 往图形上下文中设置第一条路径的信息
*/
// 第一条路径的起点
CGContextMoveToPoint(context, 100, 100);
// 第一条路径的终点(添加一条直线到路径的终点)
CGContextAddLineToPoint(context, 200, 200);
// 第一条路径的颜色
CGContextSetRGBStrokeColor(context, 1, 0, 0, 1);
// 第一条路径的宽度
CGContextSetLineWidth(context, 11);
// 第一条路径的首尾样式
CGContextSetLineCap(context, kCGLineCapRound);
// 第一条路径的转角样式
CGContextSetLineJoin(context, kCGLineJoinRound);
/*
3.1 绘制并渲染第一条路径到view.layer
*/
CGContextStrokePath(context);
/*
2.2 往图形上下文中设置第二条路径的信息
*/
// 第二条路径的起点
CGContextMoveToPoint(context, 100, 200);
// 第二条路径的终点(添加一条直线到路径的终点)
CGContextAddLineToPoint(context, 200, 300);
// 第二条路径的颜色
CGContextSetRGBStrokeColor(context, 0, 1, 0, 1);
// 第二条路径的宽度
CGContextSetLineWidth(context, 5.5);
// 第二条路径的首尾样式
CGContextSetLineCap(context, kCGLineCapRound);
// 第二条路径的转角样式
CGContextSetLineJoin(context, kCGLineJoinRound);
/*
3.2 绘制并渲染第二条路径到view.layer
*/
CGContextStrokePath(context);
}
2.2 绘制矩形
-----------CustomView.m-----------
#import "CustomView.h"
@implementation CustomView
/// 作用:专门用来做绘图的
/// 调用时机:当前view即将显示到屏幕上的时候,系统会自动调用 || 如果我们想手动触发某个view的drawRect:方法,可以调用该view的setNeedsDisplay方法
/// @param rect 当前view的bounds,不是frame
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
// 矩形的位置和大小
CGContextAddRect(context, CGRectMake(100, 100, 100, 100));
CGContextSetRGBStrokeColor(context, 1, 0, 0, 1);
CGContextSetLineWidth(context, 11);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineJoin(context, kCGLineJoinMiter);
CGContextStrokePath(context);
}
2.3 绘制弧
-----------CustomView.m-----------
#import "CustomView.h"
@implementation CustomView
/// 作用:专门用来做绘图的
/// 调用时机:当前view即将显示到屏幕上的时候,系统会自动调用 || 如果我们想手动触发某个view的drawRect:方法,可以调用该view的setNeedsDisplay方法
/// @param rect 当前view的bounds,不是frame
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
// x,y:弧的圆心
// radius:弧的半径
// startAngle:开始的弧度
// endAngle:结束的弧度
// clockwise:0-顺时针绘制,1-逆时针绘制
CGContextAddArc(context, 100, 100, 100, 0, M_PI_2, NO);
CGContextSetRGBStrokeColor(context, 1, 0, 0, 1);
CGContextSetLineWidth(context, 11);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineJoin(context, kCGLineJoinMiter);
CGContextStrokePath(context);
}
2.4 绘制圆
-----------CustomView.m-----------
#import "CustomView.h"
@implementation CustomView
/// 作用:专门用来做绘图的
/// 调用时机:当前view即将显示到屏幕上的时候,系统会自动调用 || 如果我们想手动触发某个view的drawRect:方法,可以调用该view的setNeedsDisplay方法
/// @param rect 当前view的bounds,不是frame
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
// x,y:圆的圆心
// radius:圆的半径
// startAngle:开始的弧度
// endAngle:结束的弧度
// clockwise:0-顺时针绘制,1-逆时针绘制
CGContextAddArc(context, 100, 100, 50, 0, M_PI * 2, NO);
CGContextSetRGBFillColor(context, 1, 0, 0, 1);
CGContextSetLineWidth(context, 11);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineJoin(context, kCGLineJoinMiter);
CGContextFillPath(context);
}
二、UIBezierPath
1、什么是UIBezierPath
通过上面的一些实例,我们可能会感受到Quartz2D的C语言API调用起来有些繁琐,于是苹果在UIKit框架里提供了一个UIBezierPath,它就是对Quartz2D绘制路径能力的OC封装——即UIBezierPath方法的底层就是对Quartz2D函数的封装,所以如果我们遇到需要绘制路径的场景,可以优先考虑使用UIBezierPath,只有当遇到需要Quartz2D其它能力的场景时再考虑直接使用Quartz2D。
贝塞尔曲线是指借助Control Point绘制出来的一条平滑曲线,下面说一下它的绘制原理:
- 绘制线段AC、BC,相交于点C(Control Point)
- 在线段AC上取点D,在线段BC上取点E,使得AD : DC = CE : EB
- 连接DE,在线段DE上取点F,使得DF : FE = AD : DC = CE : EB
- 这样我们就找到了贝塞尔曲线上的一个点F,按同样的道理让点D从A移动到C、点E从C移动到B,就会找到无数个点F,把点F连接起来就可以绘制出贝塞尔曲线
2、UIBezierPath的一些实例
2.1 绘制直线
-----------CustomView.m-----------
/// 作用:专门用来做绘图的
/// 调用时机:当前view即将显示到屏幕上的时候,系统会自动调用 || 如果我们想手动触发某个view的drawRect:方法,可以调用该view的setNeedsDisplay方法
/// @param rect 当前view的bounds,不是frame
- (void)drawRect:(CGRect)rect {
// 第一条路径
UIBezierPath *bezierPath1 = [[UIBezierPath alloc] init];
[bezierPath1 moveToPoint:CGPointMake(100, 100)]; // 路径的起点
[bezierPath1 addLineToPoint:CGPointMake(200, 200)]; // 路径的终点(添加一条直线到路径的终点)
[[UIColor redColor] set]; // 路径的颜色
bezierPath1.lineWidth = 11; // 路径的宽度
bezierPath1.lineCapStyle = kCGLineCapRound; // 路径的首尾样式
bezierPath1.lineJoinStyle = kCGLineJoinRound; // 路径的转角样式
[bezierPath1 stroke];
// 第二条路径
UIBezierPath *bezierPath2 = [[UIBezierPath alloc] init];
[bezierPath2 moveToPoint:CGPointMake(100, 200)];
[bezierPath2 addLineToPoint:CGPointMake(200, 300)];
[[UIColor greenColor] set];
bezierPath2.lineWidth = 5.5;
bezierPath2.lineCapStyle = kCGLineCapRound;
bezierPath2.lineJoinStyle = kCGLineJoinRound;
[bezierPath2 stroke];
}
2.2 绘制矩形
-----------CustomView.m-----------
/// 作用:专门用来做绘图的
/// 调用时机:当前view即将显示到屏幕上的时候,系统会自动调用 || 如果我们想手动触发某个view的drawRect:方法,可以调用该view的setNeedsDisplay方法
/// @param rect 当前view的bounds,不是frame
- (void)drawRect:(CGRect)rect {
// 矩形
// rect:矩形的位置和大小
UIBezierPath *bezierPath1 = [UIBezierPath bezierPathWithRect:CGRectMake(50, 50, 50, 50)];
[[UIColor redColor] setStroke];
bezierPath1.lineWidth = 11;
bezierPath1.lineCapStyle = kCGLineCapRound;
bezierPath1.lineJoinStyle = kCGLineJoinMiter;
[bezierPath1 stroke];
// 圆角矩形
// rect:矩形的位置和大小
// cornerRadius:矩形的圆角半径
UIBezierPath *bezierPath2 = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(150, 150, 50, 50) cornerRadius:10];
[[UIColor greenColor] setStroke];
bezierPath2.lineWidth = 11;
bezierPath2.lineCapStyle = kCGLineCapRound;
bezierPath2.lineJoinStyle = kCGLineJoinMiter;
[bezierPath2 stroke];
// 指定圆角的矩形
// rect:矩形的位置和大小
// corners:哪几个角要切圆角
// cornerRadii:矩形的圆角半径
UIBezierPath *bezierPath3 = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(250, 250, 50, 50) byRoundingCorners:(UIRectCornerTopLeft | UIRectCornerBottomRight) cornerRadii:CGSizeMake(10, 0)];
[[UIColor blueColor] setStroke];
bezierPath3.lineWidth = 11;
bezierPath3.lineCapStyle = kCGLineCapRound;
bezierPath3.lineJoinStyle = kCGLineJoinMiter;
[bezierPath3 stroke];
}
2.3 绘制弧
-----------CustomView.m-----------
/// 作用:专门用来做绘图的
/// 调用时机:当前view即将显示到屏幕上的时候,系统会自动调用 || 如果我们想手动触发某个view的drawRect:方法,可以调用该view的setNeedsDisplay方法
/// @param rect 当前view的bounds,不是frame
- (void)drawRect:(CGRect)rect {
UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
// center:弧的圆心
// radius:弧的半径
// startAngle:开始的弧度
// endAngle:结束的弧度
// clockwise:YES-顺时针绘制,NO-逆时针绘制
[bezierPath addArcWithCenter:CGPointMake(100, 100) radius:100 startAngle:0 endAngle:M_PI_2 clockwise:YES];
[[UIColor redColor] set];
bezierPath.lineWidth = 11;
bezierPath.lineCapStyle = kCGLineCapRound;
bezierPath.lineJoinStyle = kCGLineJoinMiter;
[bezierPath stroke];
}
2.4 绘制圆
-----------CustomView.m-----------
/// 作用:专门用来做绘图的
/// 调用时机:当前view即将显示到屏幕上的时候,系统会自动调用 || 如果我们想手动触发某个view的drawRect:方法,可以调用该view的setNeedsDisplay方法
/// @param rect 当前view的bounds,不是frame
- (void)drawRect:(CGRect)rect {
// 圆
UIBezierPath *bezierPath1 = [[UIBezierPath alloc] init];
// center:圆的圆心
// radius:圆的半径
// startAngle:开始的弧度
// endAngle:结束的弧度
// clockwise:YES-顺时针绘制,NO-逆时针绘制
[bezierPath1 addArcWithCenter:CGPointMake(100, 100) radius:50 startAngle:0 endAngle:M_PI * 2 clockwise:YES];
[[UIColor redColor] set];
bezierPath1.lineWidth = 11;
bezierPath1.lineCapStyle = kCGLineCapRound;
bezierPath1.lineJoinStyle = kCGLineJoinMiter;
[bezierPath1 stroke];
// 椭圆
// rect:给定一个矩形,通过该矩形来画出其内切椭圆
UIBezierPath *bezierPath2 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(250, 250, 100, 50)];
[[UIColor orangeColor] set];
bezierPath2.lineWidth = 11;
bezierPath2.lineCapStyle = kCGLineCapRound;
bezierPath2.lineJoinStyle = kCGLineJoinMiter;
[bezierPath2 fill];
}
2.5 绘制一条二次贝塞尔曲线和一条三次贝塞尔曲线
-----------CustomView.m-----------
/// 作用:专门用来做绘图的
/// 调用时机:当前view即将显示到屏幕上的时候,系统会自动调用 || 如果我们想手动触发某个view的drawRect:方法,可以调用该view的setNeedsDisplay方法
/// @param rect 当前view的bounds,不是frame
- (void)drawRect:(CGRect)rect {
// 二次贝塞尔曲线
UIBezierPath *bezierPath1 = [[UIBezierPath alloc] init];
[bezierPath1 moveToPoint:CGPointMake(100, 100)]; // 起点
[bezierPath1 addQuadCurveToPoint:CGPointMake(300, 100) controlPoint:CGPointMake(200, 200)]; // 终点和controlPoint
[[UIColor redColor] setStroke];
bezierPath1.lineWidth = 11;
bezierPath1.lineCapStyle = kCGLineCapRound;
bezierPath1.lineJoinStyle = kCGLineJoinMiter;
[bezierPath1 stroke];
// 三次贝塞尔曲线
UIBezierPath *bezierPath2 = [[UIBezierPath alloc] init];
[bezierPath2 moveToPoint:CGPointMake(100, 300)];
[bezierPath2 addCurveToPoint:CGPointMake(300, 300) controlPoint1:CGPointMake(150, 350) controlPoint2:CGPointMake(250, 250)]; // 终点和controlPoint1、controlPoint2
[[UIColor greenColor] setStroke];
bezierPath2.lineWidth = 11;
bezierPath2.lineCapStyle = kCGLineCapRound;
bezierPath2.lineJoinStyle = kCGLineJoinMiter;
[bezierPath2 stroke];
}
2.6 画板
第一步:我们先做一个画板,能随便绘制笔迹。(这一步仅仅用到Quartz2D绘制任意路径的能力,所以我们依旧优先考虑使用UIBezierPath)
-----------DrawingBoardForegroundView.m-----------
#import "DrawingBoardForegroundView.h"
/// 画板的实现比较简单,一共分两步:
/// 第一步:用UIResponder的touchesMove方法来画路径,并把画的路径数组给记录下来
/// 第二步:在drawRect方法里,用贝塞尔曲线绘制记录的路径数组
@interface DrawingBoardForegroundView ()
/// 每一次画的路径
@property (nonatomic, strong) UIBezierPath *bezierPath;
/// 画的路径数组
@property (nonatomic, strong) NSMutableArray *bezierPathArray;
@end
@implementation DrawingBoardForegroundView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor clearColor];
}
return self;
}
/// 作用:专门用来做绘图的
/// 调用时机:当前view即将显示到屏幕上的时候,系统会自动调用 || 如果我们想手动触发某个view的drawRect:方法,可以调用该view的setNeedsDisplay方法
/// @param rect 当前view的bounds,不是frame
- (void)drawRect:(CGRect)rect {
for (UIBezierPath *bezierPath in self.bezierPathArray) {
// 画笔
[[UIColor redColor] setStroke]; // 路径颜色
[bezierPath strokeWithBlendMode:(kCGBlendModeNormal) alpha:1.0]; // 路径模式
bezierPath.lineWidth = 10; // 路径宽度
bezierPath.lineCapStyle = kCGLineCapRound; // 路径开始和结尾的样式
bezierPath.lineJoinStyle = kCGLineJoinRound; // 路径转角处的样式
[bezierPath stroke];
}
}
#pragma mark - 原始指针事件,第一步:用UIResponder的touchesMove方法来画路径,并把画的路径数组给记录下来
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 触摸的手指可能有多个,任选其一
UITouch *touch = [touches anyObject];
// 获取开始触摸的点在画板坐标系统下的位置
CGPoint beganPoint = [touch locationInView:self];
// 每一次画的路径
self.bezierPath = [UIBezierPath bezierPath];
// 把开始触摸的点追加到当前路径上
[self.bezierPath moveToPoint:beganPoint];
// 记录每一次画的路径
[self.bezierPathArray addObject:self.bezierPath];
// 重绘
[self setNeedsDisplay];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
// 触摸的手指可能有多个,任选其一
UITouch *touch = [touches anyObject];
// 获取实时触摸的点在画板坐标系统下的位置
CGPoint movedPoint = [touch locationInView:self];
// 把实时触摸的点追加到当前路径上
[self.bezierPath addLineToPoint:movedPoint];
// 重绘
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// 触摸的手指可能有多个,任选其一
UITouch *touch = [touches anyObject];
// 获取结束触摸的点在画板坐标系统下的位置
CGPoint endedPoint = [touch locationInView:self];
// 把实时触摸的点追加到当前路径上
[self.bezierPath addLineToPoint:endedPoint];
// 重绘
[self setNeedsDisplay];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
// 触摸的手指可能有多个,任选其一
UITouch *touch = [touches anyObject];
// 获取取消触摸的点在画板坐标系统下的位置
CGPoint cancelledPoint = [touch locationInView:self];
// 把取消触摸的点追加到当前路径上
[self.bezierPath addLineToPoint:cancelledPoint];
// 重绘
[self setNeedsDisplay];
}
#pragma mark - setter, getter
- (NSMutableArray *)bezierPathArray {
if (_bezierPathArray == nil) {
_bezierPathArray = [NSMutableArray array];
}
return _bezierPathArray;
}
@end
2.7 生成图片
第二步:接下来我们可能想把画板上绘制的笔迹给生成一张图片,以便能在不同的端流转(如手机端绘制的内容可以流转到Pad端或桌面端)。(这一步要用到Quartz2D生成图片的能力,所以我们得直接使用Quartz2D了)
-----------DrawingBoardForegroundView.m-----------
#import "DrawingBoardForegroundView.h"
@implementation DrawingBoardForegroundView
/// 作用:专门用来做绘图的
/// 调用时机:当前view即将显示到屏幕上的时候,系统会自动调用 || 如果我们想手动触发某个view的drawRect:方法,可以调用该view的setNeedsDisplay方法
/// @param rect 当前view的bounds,不是frame
- (void)drawRect:(CGRect)rect {
// 路径
UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
[bezierPath moveToPoint:CGPointMake(100, 100)]; // 路径的起点
[bezierPath addLineToPoint:CGPointMake(200, 200)]; // 路径的终点(添加一条直线到路径的终点)
[[UIColor redColor] set]; // 路径的颜色
bezierPath.lineWidth = 11; // 路径的宽度
bezierPath.lineCapStyle = kCGLineCapRound; // 路径的首尾样式
bezierPath.lineJoinStyle = kCGLineJoinRound; // 路径的转角样式
[bezierPath stroke];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/*
把某个view上的东西生成图片很简单
我们只需要调用view.layer的renderInContext:方法把view上的东西渲染到一个Bitmap上下文里,
然后调用UIGraphicsGetImageFromCurrentImageContext()函数生成图片即可。
*/
// 1、开启一个Bitmap上下文
// size:Bitmap上下文的大小
// opaque:Bitmap上下文是否不透明
// scale:Bitmap上下文的比例因子(如果设置为0,则比例因子为设备主屏幕的比例因子)
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);
// 2、把view上的东西渲染到Bitmap上下文里
CGContextRef context = UIGraphicsGetCurrentContext();
[self.layer renderInContext:context];
// 3、生成图片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// 4、关闭Bitmap上下文
UIGraphicsEndImageContext();
// test:我们把生成的图片保存到桌面上看看
[UIImagePNGRepresentation(image) writeToFile:@"/Users/yiyi/Desktop/test.png" atomically:YES];
}
@end
2.8 绘制图片
第三步:接下来我们可能想把其它端流转到手机端的笔迹图片给绘制到画板上,以便能够用橡皮擦擦除(当然你也可以在此基础上继续绘制新笔迹)。(这一步要用到Quartz2D绘制图片的能力,所以我们得直接使用Quartz2D了)
-----------DrawingBoardForegroundView.m-----------
#import "DrawingBoardForegroundView.h"
@interface DrawingBoardForegroundView ()
@end
@implementation DrawingBoardForegroundView
/// 作用:专门用来做绘图的
/// 调用时机:当前view即将显示到屏幕上的时候,系统会自动调用 || 如果我们想手动触发某个view的drawRect:方法,可以调用该view的setNeedsDisplay方法
/// @param rect 当前view的bounds,不是frame
- (void)drawRect:(CGRect)rect {
UIImage *image = [UIImage imageNamed:@"前景图.png"];
// 指定位置,把原始大小的图片绘制出来
// [image drawAtPoint:CGPointZero];
// 指定位置和大小,把图片fill到这个区域里(如果大小和图片大小不一样,可能会拉伸)
[image drawInRect:CGRectMake(10, 40, image.size.width, image.size.height)];
}
@end
2.9 擦除图片
第四步:我们可以擦除图片上的像素。(橡皮擦本质上还是绘制任意路径,只不过路径是透明的,所以这一步也是仅仅用到Quartz2D绘制任意路径的能力,我们依旧优先考虑使用UIBezierPath)
-----------DrawingBoardForegroundView.m-----------
#import "DrawingBoardForegroundView.h"
@interface DrawingBoardForegroundView ()
/// 每一次画的路径
@property (nonatomic, strong) UIBezierPath *bezierPath;
/// 画的路径数组
@property (nonatomic, strong) NSMutableArray *bezierPathArray;
@end
@implementation DrawingBoardForegroundView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// 注意:橡皮擦的这两行代码要想生效,必须设置当前view的背景颜色为clearColor,否则橡皮擦的颜色擦出来是黑色
// 所以为了使画板能有各种各样的背景颜色甚至是一张背景图片,我们的做法就是把画板分为画板背景 + 画板前景,当前view仅仅是画板前景
self.backgroundColor = [UIColor clearColor];
}
return self;
}
/// 作用:专门用来做绘图的
/// 调用时机:当前view即将显示到屏幕上的时候,系统会自动调用 || 如果我们想手动触发某个view的drawRect:方法,可以调用该view的setNeedsDisplay方法
/// @param rect 当前view的bounds,不是frame
- (void)drawRect:(CGRect)rect {
UIImage *image = [UIImage imageNamed:@"前景图.png"];
// 指定位置,把原始大小的图片绘制出来
// [image drawAtPoint:CGPointZero];
// 指定位置和大小,把图片fill到这个区域里(如果大小和图片大小不一样,可能会拉伸)
[image drawInRect:CGRectMake(10, 40, image.size.width, image.size.height)];
for (UIBezierPath *bezierPath in self.bezierPathArray) {
// 橡皮擦
//
// 注意:橡皮擦的这两行代码要想生效,必须设置当前view的背景颜色为clearColor,否则橡皮擦的颜色擦出来是黑色
// 所以为了使画板能有各种各样的背景颜色甚至是一张背景图片,我们的做法就是把画板分为画板背景 + 画板前景,当前view仅仅是画板前景
[[UIColor clearColor] setStroke]; // 路径颜色
[bezierPath strokeWithBlendMode:(kCGBlendModeClear) alpha:1.0]; // 路径模式
bezierPath.lineWidth = 10; // 路径宽度
bezierPath.lineCapStyle = kCGLineCapRound; // 路径开始和结尾的样式
bezierPath.lineJoinStyle = kCGLineJoinRound; // 路径转角处的样式
[bezierPath stroke];
}
}
#pragma mark - 原始指针事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 触摸的手指可能有多个,任选其一
UITouch *touch = [touches anyObject];
// 获取开始触摸的点在画板坐标系统下的位置
CGPoint beganPoint = [touch locationInView:self];
// 每一次画的路径
self.bezierPath = [UIBezierPath bezierPath];
// 把开始触摸的点追加到当前路径上
[self.bezierPath moveToPoint:beganPoint];
// 记录每一次画的路径
[self.bezierPathArray addObject:self.bezierPath];
// 重绘
[self setNeedsDisplay];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
// 触摸的手指可能有多个,任选其一
UITouch *touch = [touches anyObject];
// 获取实时触摸的点在画板坐标系统下的位置
CGPoint movedPoint = [touch locationInView:self];
// 把实时触摸的点追加到当前路径上
[self.bezierPath addLineToPoint:movedPoint];
// 重绘
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// 触摸的手指可能有多个,任选其一
UITouch *touch = [touches anyObject];
// 获取结束触摸的点在画板坐标系统下的位置
CGPoint endedPoint = [touch locationInView:self];
// 把实时触摸的点追加到当前路径上
[self.bezierPath addLineToPoint:endedPoint];
// 重绘
[self setNeedsDisplay];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
// 触摸的手指可能有多个,任选其一
UITouch *touch = [touches anyObject];
// 获取取消触摸的点在画板坐标系统下的位置
CGPoint cancelledPoint = [touch locationInView:self];
// 把取消触摸的点追加到当前路径上
[self.bezierPath addLineToPoint:cancelledPoint];
// 重绘
[self setNeedsDisplay];
}
#pragma mark - setter, getter
- (NSMutableArray *)bezierPathArray {
if (_bezierPathArray == nil) {
_bezierPathArray = [NSMutableArray array];
}
return _bezierPathArray;
}
@end