在Swift中实现撤销功能

本文讲的是在Swift中实现撤销功能,



在过去的一段时间里,有很多的Blog推出了关于他们想在Swift中所添加的动态特性的文章。事实上Swift 已经成为了一门具有相当多动态特性的语言:它拥有泛型,协议, 头等函数(译者注1:first-class function指函数可以向类一样作为参数传递),和包含很多可以的动态操作的函数的标准库,比如mapfilter等(这意味着我们可以利用更安全更灵活的函数来代替 KVC 来使用 字符串)(译者注2:KVC指Key-Value-Coding一个非正式的 Protocol,提供一种机制来间接访问对象的属性)。对于大多数人而言,特别希望介绍反射这一特性,这意味着他们可以在程序运行时进行观察和修改。

Swift中,反射机制受到很多的限制,但是你仍然你可以在代码运行的时候动态的生成和插入一些东西。 比如这里是怎样为NSCoding或者是JSON动态生成字典的实例。

今天在这里,我们将一起看一下在Swift中怎样去实现撤销功能。 其中一种方法是通过利用Objective-C中基于的反射机制所提供的NSUndoManager。通过利用struct,我们可以利用不同的方式在我们的APP中实现撤销这一功能。 在教程开始之前,请务必确保你自己已经理解了Swiftstruct的工作机制(最重要的是理解他们都是独立的拷贝)。
首先要声明的一点是,这篇文章并不是想告诉大家我们不需要对runtime进行操作,或者我们提供的是一种NSUndoManager的替代品。这篇文章只是告诉了大家一种不同的思考方式而已。

我们首先创建一个叫做UndoHistorystruct。 通常而言,创建UndoHistory时会伴随一个警告,提示只有当A是一个struct的时才会生效。为了保存所有状态信息,我们需要将其存放入一个数组之中。当我们修改了什么时,我们只需要将其push进数组中,当我们希望进行撤回时,我们将其从数组中pop出去。我们通常希望有一个初试状态,所以我们需要建立一个初始化方法:

    struct UndoHistory<A> {
        private let initialValue: A
        private var history: [A] = []
        init(initialValue: A) {
            self.initialValue = initialValue
        }
    }

举个例子,如果我们想在一个tableViewController中通过数组的方式提供撤销操作,我们可以创建这样一个struct

    var history = UndoHistory(initialValue: [1, 2, 3])

对于不同情境下的撤销操作,我们可以创建不同的struct来实现:

    struct Person {
        var name: String
        var age: Int
    }
    var personHistory = UndoHistory(initialValue: Person(name: "Chris", age: 31))

当然,我们希望获得当前的状态,同时设置当前状态。(换句话说:我们希望实时地操作我们的历史记录)。我们可以从history数组中的最后一项值来获取我们的状态,同时如果数组为空的话,我们便返回我们的初始值。 我们可以通过将当前状态添加至history数组来改变我们的操作状态。

    extension UndoHistory {
        var currentItem: A {
            get {
                return history.last ?? initialValue
            }
            set {
                history.append(newValue)
            }
        }
    }

比如,如果我们想修改个人年龄(译者注3:指前面作者编写的Person结构体中的age属性), 我们可以通过重新计算属性来很轻松的做到这一点:

    personHistory.currentItem.age += 1
    personHistory.currentItem.age // Prints 32

当然,undo 方法的编写并未完成。对于从数组中移出最后一个元素来讲是非常简单的。 根据你自己的声明,你可以在数组为空的时候抛出一个异常,不过,我没有选择这样一种做法。

    extension UndoHistory {
        mutating func undo() {
            guard !history.isEmpty else { return }
            history.removeLast()
        }
    }

很简单的使用它(译者注4:这里指作者前面所编写的undo相关代码)

    personHistory.undo()
    personHistory.currentItem.age // Prints 31 again
~

当然,我们到现在的UndoHistory操作只是基于一个很简单的Person类。比如,如果我们想利用Array来实现一个tableviewcontrollerundo操作,我们可以利用属性来获取从数组中得到的元素:

    final class MyTableViewController<item>: UITableViewController {
        var data: UndoHistory<[item]>

        init(value: [Item]) {
            data = UndoHistory(initialValue: value)
            super.init(style: .Plain)
        }

        override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return data.currentItem.count
        }

        override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCellWithIdentifier("Identifier", forIndexPath: indexPath)
            let item = data.currentItem[indexPath.row]
            // configure `cell` with `item`
            return cell
        }

        override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
            guard editingStyle == .Delete else { return }
            data.currentItem.removeAtIndex(indexPath.row)
        }
    }

struct中另一个非常爽的特性是:我们可以自由的使用监听者模式。 比如,我们可以修改data的值:

    var data: UndoHistory<[item]> {
        didSet {
            tableView.reloadData()
        }
    }

我们即使是修改数组内很深的值(比如:data.currentItem[17].name = "John"),我们通过didSet也能很方便地定位到修改的地方。当然,我们可能希望做一些例如reloadData这样方便的事情。比如, 我们可以利用Changeset 库来计算变化,然后来根据插入/删除/移动/等不同的操作来添加动画。

很明显的是, 这种方法有着它自身的缺点。例如,它保存了整个状态的历史操作,不是每次状态变化之间的不同点。 这种方法只使用了struct来实现undo操作 (更为准确的讲:是只使用了struct中值的一些特性)。这意味着,你并不需要去阅读 runtime编程指导这本书, 你只需要对structgenerics(译者注5:generics指泛型)有足够的了解。

  1. 为data.currentItem提供了一个可计算的属性 items 来进行获取和设置操作,是一个不错的想法。这使得data-sourcedelegate等方法的实现变得更为容易。
  2. 如果你想更进一步优化,这里有一些非常有意思的想法:添加恢复功能,或者是编辑功能。你可以在tableView中去实现, 如果你真的很天真的按照这个去做了,那么你会发现在你的undo历史中会存在重复记录。

    关于翻墙(硬广)

    这只是一个友情推荐,我现在使用的是一起艾斯,ipv4,ipv6线路全覆盖,唔,站长人也很好,基本你的合理需求,站长都能满足。建议可以试试。

作者:Zheaoli
链接:http://www.jianshu.com/p/ba6af299aeec
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。






原文发布时间为:2016年06月15日


本文来自合作伙伴掘金,了解相关信息可以关注掘金网站。

时间: 2024-08-05 19:53:55

在Swift中实现撤销功能的相关文章

在Word 2010中使用“撤销键入”或“恢复键入”功能

在编辑Word 2010文档的时候,如果所做的操作不合适,而想返回到当前结果前面的状态,则可以通过"撤销键入"或"恢复键入"功能实现."撤销"功能可以保留最近执行的操作记录,用户可以按照从后到前的顺序撤销若干步骤,但不能有选择地撤销不连续的操作.用户可以按下Alt+Backspace组合键执行撤销操作,也可以单击"快速访问工具栏"中的"撤销键入"按钮,如图2009122901所示. 图2009122901

在Word2010中使用“撤销键入”或“恢复键入”功能

在编辑Word2010文档的时候,如果所做的操作不合适,而想返回到当前结果前面的状态,则可以通过"撤销键入"或"恢复键入"功能实现."撤销"功能可以保留最近执行的操作记录,用户可以按照从后到前的顺序撤销若干步骤,但不能有选择地撤销不连续的操作.用户可以按下Alt+Backspace组合键执行撤销操作,也可以单击"快速访问工具栏"中的"撤销键入"按钮,如图1所示. 图1 单击"撤销键入"按

vb-Vb中如何编码撤销功能,也就是返回上一步的操作!求代码

问题描述 Vb中如何编码撤销功能,也就是返回上一步的操作!求代码 5C Vb中如何编码撤销功能,也就是返回上一步的操作!求代码!求解答! 解决方案 直接往你的文本框发送 wm_undo 消息Declare Function SendMessage Lib ""user32"" Alias ""SendMessageA"" (ByVal hwnd As Long _ByVal wMsg As Long ByVal wParam

Swift中使用可选类型完美解决占位问题

  这篇文章主要介绍了Swift中使用可选类型完美解决占位问题,本文讲解了为Dictionary增加objectsForKeys函数.Swift中更简便的方法.内嵌可选类型等内容,需要的朋友可以参考下 可选类型是Swift中新引入的,功能很强大.在这篇博文里讨论的,是在Swift里,如何通过可选类型来保证强类型的安全性.作为例子,我们来创建一个Objective-C API的Swift版本,但实际上Swift本身并不需要这样的API. 为Dictionary增加objectsForKeys函数

Swift中的访问控制和protected

  这篇文章主要介绍了Swift中的访问控制和protected,本文主要讲解为什么Swift没有类似protected的选项,需要的朋友可以参考下 原文再续,书折第一回. 很多其他编程语言都有一种"protected"设定,可以限制某些类方法只能被它的子类所使用. Swift支持了访问控制后,大家给我们的反馈都很不错.而有的开发者问我们:"为什么Swift没有类似protected的选项?" 当我们在设计Swift访问控制的不同等级时,我们认为有两种主要场景: ●

Swift中的Access Control权限控制介绍

  这篇文章主要介绍了Swift中的Access Control权限控制介绍,本文讲解了private.internal.public三个关键字的使用,需要的朋友可以参考下 如果您之前没有接触过权限控制,先来听一个小故事: 小明是五道口工业学院的一个大一新生,最近他有点烦恼,因为同屋经常用他的热水壶,好像那是自己家的一样,可是碍于同学情面,又不好意思说.直到有一天,他和学姐小K吐槽. 学姐听了之后,说:大学集体生活里面,大部分东西都是默认室友可以共用的.如果你不想别人拿,我可以帮你做封印,只要打

Swift中类似C++和ruby中的final机制

大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 我们知道在C++和ruby语言的错误处理中有一种final机制,发生在无论是否出现错误都会执行的情况.这时适合处理收尾或清理工作. 在Swift中同样存在一个defer语法,后面跟闭包可以完成类似的功能,只不过defer不仅仅可以用在错误处理中,它可以用在任何需要清理的情况: var isTrue = false func test(name:String)-

[译] Swift 中关于并发的一切:第一部分 — 当前

本文讲的是[译] Swift 中关于并发的一切:第一部分 - 当前, 原文地址:All about Concurrency in Swift - Part 1: The Present 原文作者:Umberto Raimondi 译文出自:掘金翻译计划 译者:Deepmissea 校对者:Feximin,zhangqippp Swift 中关于并发的一切:第一部分 - 当前 在 Swift 语言的当前版本中,并没有像其他现代语言如 Go 或 Rust 一样,包含任何原生的并发功能. 如果你计划异

Swift中的闭包(Closure)[转]

闭包在Swift中非常有用.通俗的解释就是一个Int类型里存储着一个整数,一个String类型包含着一串字符,同样,闭包是一个包含着函数的类型.有了闭包,你就可以处理很多在一些古老的语言中不能处理的事情.这是因为闭包使用的多样性,比如你可以将闭包赋值给一个变量,你也可以将闭包作为一个函数的参数,你甚至可以将闭包作为一个函数的返回值.它的强大之处可见一斑. 在Swift的很多文档教材中都说函数是"一等公民",起初我还不是很理解"一等公民"是什么意思,但当我理解了闭包以