在Swift中实现撤销功能
在过去的一段时间里,有很多的Blog推出了关于他们想在Swift中所添加的动态特性的文章。事实上Swift 已经成为了一门具有相当多动态特性的语言:它拥有泛型,协议, 头等函数(译者注1:first-class function指函数可以向类一样作为参数传递),和包含很多可以的动态操作的函数的标准库,比如map和filter等(这意味着我们可以利用更安全更灵活的函数来代替 KVC 来使用 字符串)(译者注2:KVC指Key-Value-Coding一个非正式的 Protocol,提供一种机制来间接访问对象的属性)。对于大多数人而言,特别希望介绍反射这一特性,这意味着他们可以在程序运行时进行观察和修改。
在Swift中,反射机制受到很多的限制,但是你仍然你可以在代码运行的时候动态的生成和插入一些东西。 比如这里是怎样为NSCoding或者是JSON动态生成字典的实例。
今天在这里,我们将一起看一下在Swift中怎样去实现撤销功能。 其中一种方法是通过利用Objective-C中基于的反射机制所提供的NSUndoManager。通过利用struct,我们可以利用不同的方式在我们的APP中实现撤销这一功能。 在教程开始之前,请务必确保你自己已经理解了Swift中struct的工作机制(最重要的是理解他们都是独立的拷贝)。
首先要声明的一点是,这篇文章并不是想告诉大家我们不需要对runtime进行操作,或者我们提供的是一种NSUndoManager的替代品。这篇文章只是告诉了大家一种不同的思考方式而已。
我们首先创建一个叫做UndoHistory的struct。 通常而言,创建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来实现一个tableviewcontroller的undo操作,我们可以利用属性来获取从数组中得到的元素:
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编程指导这本书, 你只需要对struct和generics(译者注5:generics指泛型)有足够的了解。
为data.currentItem提供了一个可计算的属性 items 来进行获取和设置操作,是一个不错的想法。这使得data-source和delegate等方法的实现变得更为容易。 如果你想更进一步优化,这里有一些非常有意思的想法:添加恢复功能,或者是编辑功能。你可以在tableView中去实现, 如果你真的很天真的按照这个去做了,那么你会发现在你的undo历史中会存在重复记录。iOS 仿支付宝银行卡界面(支持Swift/OC) 在有支付相关的APP中,都有对应的钱包,虽然现在的支付宝,微信支付很流行,但是都是需要绑定自己的银行卡,那么这个银行卡的卡包页面该怎么实现呢?在网上找了许久也没有找到合适的,那就索性自己造轮子。
相关文章
- 实现toggleClass功能
- Laravel + Vue + element-ui 【前端项目一】vue 实现查看更多 5
- 网页计算器功能【案例】【两种实现】
- 实现左右两边用户添加、删除、调位置(上下移动)、排序等的功能
- SwiftUI 组件之SecureField实现Placeholder功能(教程)
- Python+ITchart实现微信机器人对指定的朋友和群自动回复
- Swift 基础100问之 02 在Swift中如何实现C/C++中的#ifdef功能 编译器预处理
- 淘淘商城maven工程的创建和svn的上传实现
- 使用node+vue实现简单的WebSocket聊天功能
- Go开发中的JWT的创建和解析的功能实现
- 一个帖子教你实现 商品下单、获取商品订单信息、商品送货时发送通知 一体化功能
- blender 能实现哪些功能? 能干啥? 编程 和 建模 动画 的结合。
- 使用NGINX+LUA实现WAF功能 和nginx 防盗链
- 【Verilog基础】CMOS逻辑门实现基础功能(反相器/与非门/或非门/与门/或门)
- Unity 工具 之 Natcorder 插件实现录频、拍照jpg、生成Gif动图功能(适用移动端)
- Web 前端 之 html 移动端/PC端元素长按功能的简单封装,实现元素长按事件的触发