在Swift中实现撤销功能

简介: 本文讲的是在Swift中实现撤销功能,在过去的一段时间里,有很多的Blog推出了关于他们想在Swift中所添加的动态特性的文章。事实上Swift 已经成为了一门具有相当多动态特性的语言:它拥有泛型,协议, 头等函数(译者注1:first-class function指函数可以向类一样作为参数传递)
本文讲的是在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历史中会存在重复记录。




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





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

本文来自云栖社区合作伙伴掘金,了解相关信息可以关注掘金网站。
目录
相关文章
|
缓存 索引
Swift 绘图板功能完善以及终极优化
转载请注明出处:http://blog.csdn.net/zhangao0086/article/details/45289475。
1201 0
|
算法 iOS开发
Swift 全功能的绘图板开发
转载请注明出处:http://blog.csdn.net/zhangao0086/article/details/43836789。
1276 0
|
Swift 网络架构
swift UI专项训练40 用swift实现打电话和发短信功能
   今天来讲一下如何让我们的APP可以访问系统的短信和电话功能。首先来说短信功能,比较简单,跟之前的做法差别不大,要使用UIApplication,它是一个单例。
968 0
swift UI专项训练39 用Swift实现摇一摇功能
   微信的摇一摇功能想必大家都用过,过年的时候抢红包也没少摇吧,那么用swift语言如何实现这么酷炫的功能呢。
972 0
|
4月前
|
Unix 调度 Swift
苹果iOS新手开发之Swift 中获取时间戳有哪些方式?
在Swift中获取时间戳有四种常见方式:1) 使用`Date`对象获取秒级或毫秒级时间戳;2) 通过`CFAbsoluteTimeGetCurrent`获取Core Foundation的秒数,需转换为Unix时间戳;3) 使用`DispatchTime.now()`获取纳秒级精度的调度时间点;4) `ProcessInfo`提供设备启动后的秒数,不表示绝对时间。不同方法适用于不同的精度和场景需求。
131 3
|
10天前
|
Swift iOS开发 UED
如何使用Swift和UIKit在iOS应用中实现自定义按钮动画
本文通过一个具体案例,介绍如何使用Swift和UIKit在iOS应用中实现自定义按钮动画。当用户点击按钮时,按钮将从圆形变为椭圆形,颜色从蓝色渐变到绿色;释放按钮时,动画以相反方式恢复。通过UIView的动画方法和弹簧动画效果,实现平滑自然的过渡。
24 1