在开始之前,首先感谢画板(涂鸦)实现 - iOS和iOS 画板/涂鸦 你画我猜 demo (OC版)的作者,在下从他们的的博客中获得了很多启发。还要感谢外国友人提供的 曲线优化策略。
首先从确定实现方案开始。也经历了许多挫折。因为最开始我是想用 OpenGL 来实现的。。但是由于种种原因,最后选择了 UIBezierPath 配合 CAShapeLayer 再加上 截图合成来实现。下面来看具体思路。
获取触摸的手势,都是通过相同的回调来获取的
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent*)event
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent*)event
复制代码
然后就是处理用户操作所获的点,以进行下一步的绘制,此时需记录下开始的点,然后在move 方法中进行下一步的操作,最后操作结束。
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #000000}p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px 'PingFang SC'; color: #1e9421}p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #3e1e81}p.p4 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #294c50}p.p5 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #1e9421}p.p6 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #1337ff}p.p7 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #000000; min-height: 16.0px}span.s1 {font-variant-ligatures: no-common-ligatures}span.s2 {font-variant-ligatures: no-common-ligatures; color: #539aa4}span.s3 {font-variant-ligatures: no-common-ligatures; color: #0435ff}span.s4 {font: 14.0px Menlo; font-variant-ligatures: no-common-ligatures; color: #000000}span.s5 {font: 14.0px Menlo; font-variant-ligatures: no-common-ligatures}span.s6 {font-variant-ligatures: no-common-ligatures; color: #c42275}span.s7 {font-variant-ligatures: no-common-ligatures; color: #31595d}span.s8 {font-variant-ligatures: no-common-ligatures; color: #78492a}span.s9 {font-variant-ligatures: no-common-ligatures; color: #000000}span.s10 {font-variant-ligatures: no-common-ligatures; color: #294c50}span.s11 {font-variant-ligatures: no-common-ligatures; color: #1e9421}span.s12 {font: 14.0px 'PingFang SC'; font-variant-ligatures: no-common-ligatures; color: #1e9421}span.s13 {font-variant-ligatures: no-common-ligatures; color: #1337ff}span.s14 {font-variant-ligatures: no-common-ligatures; color: #3e1e81}span.s15 {font-variant-ligatures: no-common-ligatures; color: #703daa}span.s16 {font-variant-ligatures: no-common-ligatures; color: #6122ae}span.s17 {font-variant-ligatures: no-common-ligatures; color: #c81b13}
ctr = 0;
brush.endPoint = currentPoint;
// 默认画线,点击同一个点时,添加黑点
if (brush.shapeType == LxShapeDefault && CGPointEqualToPoint(brush.beginPoint, brush.endPoint)) {
[brush.bezierPath addArcWithCenter:currentPoint radius:self.lindWidth/4.0 startAngle:0 endAngle:2*M_PI clockwise:NO];
} else if (brush.shapeType == LxShapeEraser) {
[brush.bezierPath addArcWithCenter:currentPoint radius:self.lindWidth/4.0 startAngle:0 endAngle:2*M_PI clockwise:NO];
}
if (brush.shapeType == LxShapeEraser) {
[self drawEraser:brush];
} else {
[self.canvas setBrush:brush];
}
[self storeCurrentImage];
} else { // 移动
if (brush.shapeType == LxShapeDefault || brush.shapeType == LxShapeEraser) {
// CGPoint centerPoint = CGPointMake((currentPoint.x+self.lastPoint.x)*0.5, (currentPoint.y+self.lastPoint.y)*0.5);
// [brush.bezierPath addQuadCurveToPoint:currentPoint controlPoint:centerPoint];
// 此处优化策略,参考
// https://code.tutsplus.com/tutorials/smooth-freehand-drawing-on-ios--mobile-13164
ctr++;
pts[ctr] = currentPoint;
if (ctr == 4) {
pts[3] = CGPointMake((pts[2].x + pts[4].x)/2.0, (pts[2].y + pts[4].y)/2.0); // move the endpoint to the middle of the line joining the second control point of the first Bezier segment and the first control point of the second Bezier segment
[brush.bezierPath moveToPoint:pts[0]];
[brush.bezierPath addCurveToPoint:pts[3] controlPoint1:pts[1] controlPoint2:pts[2]]; // add a cubic Bezier from pt[0] to pt[3], with control points pt[1] and pt[2]
// replace points and get ready to handle the next segment
pts[0] = pts[3];
pts[1] = pts[4];
ctr = 1;
}
} else if (brush.shapeType == LxShapeLine) {
[brush.bezierPath removeAllPoints];
[brush.bezierPath moveToPoint:brush.beginPoint];
[brush.bezierPath addLineToPoint:currentPoint];
} else if (brush.shapeType == LxShapeEllipse) {
brush.bezierPath = [UIBezierPath bezierPathWithOvalInRect:[self getRectWithStartPoint:brush.beginPoint endPoint:currentPoint]];
} else if (brush.shapeType == LxShapeRect) {
brush.bezierPath = [UIBezierPath bezierPathWithRect:[self getRectWithStartPoint:brush.beginPoint endPoint:currentPoint]];
// } else if (brush.shapeType == LxShapeEraser) {
} else {
NSLog(@"%ld", brush.shapeType);
}
if (brush.shapeType == LxShapeEraser) {
[self drawEraser:brush];
} else {
[self.canvas setBrush:brush];
}
self.lastPoint = currentPoint;
}
}
复制代码
其中绘制线段、椭圆、矩形的部分不再赘述。
关于撤销和恢复的部分是基于每次操作后的截图,把截图异步保存到本地指定文件夹内,然后通过当前所在的索引切换,即可实现撤销和恢复。
其中的录屏(有音频)功能使用 ReplayKit 来简单实现。如果只是了无音频的录屏,可参考ASScreenRecorder.
Demo链接 , 觉得好的请点一个 Star。