当前位置: 首页 > news >正文

iOS 画板 涂鸦 答题

在开始之前,首先感谢画板(涂鸦)实现 - 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。

相关文章:

  • poj 1475 Pushing Boxes
  • 初识 ActivityLifecycleCallbacks
  • Zim - 普通人的Org-mode
  • 带参数存储过程的小例子
  • NSLog输出对象
  • 需要Review的题目:
  • lame的ios 静态库创建shell
  • 浅谈设计模式在iOS开发实战项目中的应用
  • string的Equels问题小记
  • JS创建函数:函数声明和函数表达式
  • 快给你的app上锁吧(android数字解锁)
  • 2012财富世界500强发布,大陆首超日本,新增12家
  • F#中的事件(下)
  • BW数据源深入研究【转自WKingChen的博客】
  • JsonModel 的使用
  • 【干货分享】SpringCloud微服务架构分布式组件如何共享session对象
  • 230. Kth Smallest Element in a BST
  • ComponentOne 2017 V2版本正式发布
  • el-input获取焦点 input输入框为空时高亮 el-input值非法时
  • iOS 系统授权开发
  • Java小白进阶笔记(3)-初级面向对象
  • linux安装openssl、swoole等扩展的具体步骤
  • node 版本过低
  • Spring核心 Bean的高级装配
  • Vue.js源码(2):初探List Rendering
  • 多线程事务回滚
  • 基于 Babel 的 npm 包最小化设置
  • 微信支付JSAPI,实测!终极方案
  • 写给高年级小学生看的《Bash 指南》
  • 移动端解决方案学习记录
  • 终端用户监控:真实用户监控还是模拟监控?
  • (06)金属布线——为半导体注入生命的连接
  • (1)STL算法之遍历容器
  • (13):Silverlight 2 数据与通信之WebRequest
  • (6)添加vue-cookie
  • (7)STL算法之交换赋值
  • (arch)linux 转换文件编码格式
  • (Redis使用系列) Springboot 整合Redisson 实现分布式锁 七
  • (编程语言界的丐帮 C#).NET MD5 HASH 哈希 加密 与JAVA 互通
  • (分布式缓存)Redis分片集群
  • (附源码)ssm基于微信小程序的疫苗管理系统 毕业设计 092354
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(七)
  • (实战篇)如何缓存数据
  • (原創) 博客園正式支援VHDL語法著色功能 (SOC) (VHDL)
  • (转)nsfocus-绿盟科技笔试题目
  • .NET 5种线程安全集合
  • .net 开发怎么实现前后端分离_前后端分离:分离式开发和一体式发布
  • .NET 使用 ILRepack 合并多个程序集(替代 ILMerge),避免引入额外的依赖
  • .NET/C# 在代码中测量代码执行耗时的建议(比较系统性能计数器和系统时间)...
  • .net分布式压力测试工具(Beetle.DT)
  • .pub是什么文件_Rust 模块和文件 - 「译」
  • [ C++ ] STL priority_queue(优先级队列)使用及其底层模拟实现,容器适配器,deque(双端队列)原理了解
  • [23] GaussianAvatars: Photorealistic Head Avatars with Rigged 3D Gaussians
  • [bzoj1901]: Zju2112 Dynamic Rankings
  • [C# WPF] 如何给控件添加边框(Border)?