最近在做一个签字版的模块,需要用到了贝塞尔曲线来画。其实,做这种画板有很多方法,可以用UIGraphics来画,也可以用OPenGL来画,只是当时选择了贝塞尔曲线,没想到还入了坑。
看下面两张图:
明显的能够看到下图要比上图圆滑好看一些。
因为我们再使用贝塞尔曲线的时候一般是:
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(20, 20)];
[path addLineToPoint:CGPointMake(self.frame.size.width - 40, 20)];
[path stroke];
这样就完事了,但是这样的结果就是第二张图所示,所以我们为了达到图一的效果就要用到:
-(void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;
这个函数,这是创建二次贝塞尔曲线的函数
参数:endPoint->终点
controlPoint->控制点
如图:
但是有一个问题就是控制点怎么获取,控制点我这里的做法就是取前一个点和当前点的中点:
取中点函数
static CGPoint midpoint(CGPoint p0, CGPoint p1) {
return (CGPoint) {
(p0.x + p1.x) / 2.0,
(p0.y + p1.y) / 2.0
};
}
核心代码
在 (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
这个方法中,获取开始的点,赋值给定义的一个全局 CGPoint previousPoint
在 (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
这个函数中获取滑动中的点
UITouch *myTouche = [touches anyObject];
CGPoint point = [myTouche locationInView:self];
//取中点
CGPoint midPoint = midpoint(previousPoint,point);
//用定义过的UIBezierPath对象 去加载二次贝塞尔曲线
[self.bezier addQuadCurveToPoint:midPoint controlPoint:previousPoint];
//再将当前点赋值给 previousPoint,这样就能连贯的画出线来了
previousPoint = point;
//重绘界面
[self setNeedsDisplay];
项目中同时,还包含:撤销、恢复、清除、返回、保存的功能。
这些功能的主要实现逻辑就是,将画好的贝塞尔曲线放到数组中,然后绘制,当撤销的时候就删除最后一个,放到另一个恢复数组中,清除的话就清除两个数组,重新绘制一下就行了。
完整代码:
#import <UIKit/UIKit.h>
@interface DrawView : UIView
-(instancetype)initWithPenWidth:(CGFloat )penWidth PenColor:(UIColor *)penColor;
/**
撤销
*/
-(void)undoAction;
/**
恢复
*/
-(void)recoverAction;
/**
清除
*/
- (void)clearAction;
@end
#import "DrawView.h"
NSString *const PARAM_PEN_WIDTH = @"penWidth";
NSString *const PARAM_PEN_COLOR = @"penColor";
static CGPoint midpoint(CGPoint p0, CGPoint p1) {
return (CGPoint) {
(p0.x + p1.x) / 2.0,
(p0.y + p1.y) / 2.0
};
}
@interface DrawView ()
{
CGPoint previousPoint;
}
/**`
线的颜色
*/
@property (nonatomic,strong) UIColor *lineColor;
/**
线的宽度
*/
@property (nonatomic,assign) CGFloat lineWidth;
//声明贝塞尔曲线
@property(nonatomic, strong) UIBezierPath *bezier;
//存储Undo出来的线条
@property(nonatomic, strong) NSMutableArray *cancleArray;
//用来记录已有线条
@property (nonatomic, strong) NSMutableArray *allLine;
@end
@implementation DrawView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor whiteColor];
self.allLine = [NSMutableArray arrayWithCapacity:1];
self.cancleArray = [NSMutableArray arrayWithCapacity:50];
}
return self;
}
/**
撤销
*/
-(void)undoAction{
if (self.allLine.count > 0)
{
NSInteger index = self.allLine.count - 1;
[self.cancleArray addObject:self.allLine[index]];
[self.allLine removeObjectAtIndex:index];
[self setNeedsDisplay];
}
}
/**
恢复
*/
-(void)recoverAction{
if (self.cancleArray.count > 0)
{
NSInteger index = self.cancleArray.count - 1;
[self.allLine addObject:self.cancleArray[index]];
[self.cancleArray removeObjectAtIndex:index];
[self setNeedsDisplay];
}
}
/**
清除
*/
- (void)clearAction{
[self.allLine removeAllObjects];
[self.cancleArray removeAllObjects];
[self setNeedsDisplay];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//新建贝塞斯曲线
self.bezier = [UIBezierPath bezierPath];
//获取触摸的点
UITouch *myTouche = [touches anyObject];
CGPoint point = [myTouche locationInView:self];
//把刚触摸的点设置为bezier的起点
[self.bezier moveToPoint:point];
previousPoint = point;
//把每条线存入字典中
NSMutableDictionary *tempDic = [[NSMutableDictionary alloc] initWithCapacity:3];
[tempDic setObject:self.lineColor forKey:PARAM_PEN_COLOR];
[tempDic setObject:[NSNumber numberWithFloat:self.lineWidth] forKey:PARAM_PEN_WIDTH];
[tempDic setObject:self.bezier forKey:@"line"];
//把线加入数组中
[self.allLine addObject:tempDic];
}
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UITouch *myTouche = [touches anyObject];
CGPoint point = [myTouche locationInView:self];
CGPoint midPoint = midpoint(previousPoint,point);
[self.bezier addQuadCurveToPoint:midPoint controlPoint:previousPoint];
previousPoint = point;
//重绘界面
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
//对之前的线的一个重绘过程
for (int i = 0; i < self.allLine.count; i ++)
{
NSDictionary *tempDic = self.allLine[i];
UIColor *color = tempDic[PARAM_PEN_COLOR];
CGFloat width = [tempDic[PARAM_PEN_WIDTH] floatValue];
UIBezierPath *path = tempDic[@"line"];
[color setStroke];
[path setLineWidth:width];
[path stroke];
}
}
@end