这个随笔的内容以上一个随笔为基础,(在iOS中实现一个简单的画板),上一个随笔实现了一个简单的画板:

 
今天我们要为这个画板增加Undo/Redo操作,当画错了一笔,可以撤销它,或者撤销之后后悔了,还可以还原。而且我们要通过晃动手机来触发Undo/Redo的选择。
 
这个demo使用NSUndoManager实现Undo/Redo操作,NSUndoManager 的实现原理是它作为一个记录器,每次数据变化,我们要用这个记录器记录一个相反的操作,当需要undo的时候,它通过执行这个相反的操作就可以实现了。对于这个画板demo,我们通过把array中的Line首尾连接起来实现的,所以要想还原到上次的状态,我们通过从array中remove最后添加的那条线就可以实现了。
 
NSUndoManager需要两个步骤:
第一步,注册NSUndoManger操作,既上边提到的注册一个相反的操作。(先只实现undo操作)
首先添加一个相反的操作,即一个从array中remove Line的方法,方法名就叫removeLine:
 
然后修改addLine方法,注册刚刚添加的removeLine方法:
 
但是这里有一个问题:因为当手指在屏幕移动的时候,touchMove方法会被持续多次触发,所以画一笔,是由多个Line组成的。所以addLine会调用多次,如果我们直接undo,只是remove掉了一条Line,但是画一笔是由多个Line组成,这样只能移掉一笔中的一部分,即结尾的那部分。
 
那要怎么办呢?如何一次移除多个Line,即画一笔所包含的一组Line。
NSUndoManager有一个分组的概念,就是为了解决这类问题的。
正常情况下,画一笔肯定会触发3个方法,依次是touchBegan, touchMove 和 touchEnd,所以我们可以通过touchBegan和touchEnd就能分辨出画一笔的开始和结束。既:
 
这样我们再执行撤销的时候就是一次remove一组的Line了。
 
第二步,执行Undo
在UI添加一个按钮,作为Undo按钮,按钮的点击事件中简单地调用undo方法就可以了。既:
 
Undo操作这样就算是完成了。
Redo操作和Undo操作道理一样,我们只需要修改下removeLine方法,当remove一条Line的时候,也注册一个相反的操作,即注册addLine方法,修改后的removeLine方法:
 
晃动触发Undo/Redo操作
其实我们根本不需要在UI添加Undo/Redo按钮,直接晃动手机就会自动弹出Undo/Redo选项,而且晃动的功能是自动打开的。
如果需要关闭它,需要设置:
在这个demo中,请不要将此属性设置为NO。
 
最后看下效果:(在模拟器中无法晃动,使用快捷键进行了模拟)
 

相关源代码:github

iOS: 为画板App增加 Undo/Redo(撤销/重做)操作的更多相关文章

  1. C#中泛型容器Stack<T>的用法,以及借此实现”撤销/重做”功能

    .Net为我们提供了众多的泛型集合.比如,Stack<T>先进后出,Queue<T>先进先出,List<T>集合元素可排序,支持索引,LinkedList<T ...

  2. 在iOS中实现一个简单的画板App

    在这个随笔中,我们要为iPhone实现一个简单的画板App. 首先需要指出的是,这个demo中使用QuarzCore进行绘画,而不是OpenGL.这两个都可以实现类似的功能,区别是OpenGL更快,但 ...

  3. 从Undo,Redo谈命令模式

    一般的应用软件中,通常会提供Redo和Undo的操作,比如Paint.NET中的动作面板,Word中的撤销重做,一般我们按Ctrl-Z即可回退到上次操作. 要实现上面的这一功能,最直观的想法就是,我们 ...

  4. Undo/Redo for Qt Tree Model

    Undo/Redo for Qt Tree Model eryar@163.com Abstract. Qt contains a set of item view classes that use ...

  5. IOS开始对App Store大扫除:你的APP更新了吗?

    成都亿合科技小编从北京商报了解到,对于开发APP应用的要注意啦,IOS要开始对App Store大扫除:你的APP更新了吗? 日前苹果App Store的开发者们发送邮件,表示将实施一个持续评估应用行 ...

  6. iOS UIKit:App

    1.App生命周期 IOS架构是由许多设计模式实现,如model-view-controller 和 delegation模式. 1.1 main函数 与其它框架类似,IOS框架的入口也是从main函 ...

  7. iOS 轻松使用 App 数据统计

    想获取用户各项行为数据吗? 想轻松查看用户行为图表吗? 想高效进行 App 运营管理吗? 想,来我带你玩转 App 数据统计.这里我使用专业.轻便的 JAnalytics. 本文内容分为两部分:代码示 ...

  8. ASP.NET MVC Filters 4种默认过滤器的使用【附示例】 数据库常见死锁原因及处理 .NET源码中的链表 多线程下C#如何保证线程安全? .net实现支付宝在线支付 彻头彻尾理解单例模式与多线程 App.Config详解及读写操作 判断客户端是iOS还是Android,判断是不是在微信浏览器打开

    ASP.NET MVC Filters 4种默认过滤器的使用[附示例]   过滤器(Filters)的出现使得我们可以在ASP.NET MVC程序里更好的控制浏览器请求过来的URL,不是每个请求都会响 ...

  9. 使用 WijmoJS 轻松实现撤消重做(Undo /Redo)

    使用 WijmoJS 轻松实现撤消重做(Undo /Redo) 在V2019.0 Update2 的全新版本中,WijmoJS能够轻松实现撤消和重做操作,使Web应用程序的使用更加友好.更加高效. 不 ...

随机推荐

  1. MySQL中interactive_timeout和wait_timeout的区别

    在用mysql客户端对数据库进行操作时,打开终端窗口,如果一段时间没有操作,再次操作时,常常会报如下错误: ERROR (HY000): Lost connection to MySQL server ...

  2. Java程序:从命令行接收多个数字,求和并输出结果

    一.设计思想:由于命令行接收的是字符串类型,因此应先将字符串类型转化为整型或其他字符型,然后利用for循环求和并输出结果 二.程序流程图: 三.源程序代码:   //王荣荣 2016/9/23     ...

  3. 无法访问org.springframework.core.NestedRuntimeException 找不到org.springframework.core.NestedRuntimeException的类文件

    在学习springAOP时,出现如下异常: 无法访问org.springframework.core.NestedRuntimeException 找不到org.springframework.cor ...

  4. Android之Dedug--Circular dependencies cannot exist in AnimatorSet

    今日,在学习AnimatorSet时,使用play.with.after.before时,代码书写如下: ObjectAnimator animator1 = ObjectAnimator.ofFlo ...

  5. 图解Spark API

    初识spark,需要对其API有熟悉的了解才能方便开发上层应用.本文用图形的方式直观表达相关API的工作特点,并提供了解新的API接口使用的方法.例子代码全部使用python实现. 1. 数据源准备 ...

  6. OpenGL ES: Array Texture初体验

    [TOC] Array Texture这个东西的意思是,一个纹理对象,可以存储不止一张图片信息,就是说是是一个数组,每个元素都是一张图片.这样免了频繁地去切换当前需要bind的纹理,而且可以节省系统资 ...

  7. ENode框架Conference案例分析系列之 - 文章索引

    ENode框架Conference案例分析系列之 - 业务简介 ENode框架Conference案例分析系列之 - 上下文划分和领域建模 ENode框架Conference案例分析系列之 - 架构设 ...

  8. Threadlocal使用Case

    Threadlocal能够为每个线程分配一份单独的副本,使的线程与线程之间能够独立的访问各自副本.Threadlocal 内部维护一个Map,key为线程的名字,value为对应操作的副本. /** ...

  9. [免费了] SailingEase .NET Resources Tool (.NET 多语言资源编辑器)

    这是我2010年左右,写 Winform IDE (http://www.cnblogs.com/sheng_chao/p/4387249.html)项目时延伸出的一个小项目. 最初是以共享软件的形式 ...

  10. BCL中String.Join的实现

    在开发中,有时候会遇到需要把一个List对象中的某个字段用一个分隔符拼成一个字符串的情况.比如在SQL语句的in条件中,我们通常需要把List<int>这样的对象转换为“1,2,3”这样的 ...