Kev*_*fin 11 nsundomanager ios swift
我如何UndoManager在Swift中使用?
这是我尝试复制的Objective-C示例:
[[undoManager prepareWithInvocationTarget:self] myArgumentlessMethod];
Run Code Online (Sandbox Code Playgroud)
然而,Swift似乎没有NSUndoManager,这(看似)意味着我不能调用NSInvocation它没有实现的方法.
我在Swift中尝试过基于对象的版本,但它似乎崩溃了我的Playground:
undoManager.registerUndoWithTarget(self, selector: Selector("myMethod"), object: nil)
Run Code Online (Sandbox Code Playgroud)
然而它似乎崩溃,即使我的对象接受类型的参数 undoManager
在Swift中执行此操作的最佳方法是什么?有没有办法避免使用基于对象的注册发送不必要的对象?
Jos*_*zzi 24
OS X 10.11+/iOS 9+更新
(在Swift 3中也一样)
OS X 10.11和iOS 9引入了一项新NSUndoManager功能:
public func registerUndoWithTarget<TargetType>(target: TargetType, handler: TargetType -> ())
Run Code Online (Sandbox Code Playgroud)
例
想象一下视图控制器(self在此示例中为类型MyViewController)和Person具有存储属性的模型对象name.
func setName(name: String, forPerson person: Person) {
// Register undo
undoManager?.registerUndoWithTarget(self, handler: { [oldName = person.name] (MyViewController) -> (target) in
target.setName(oldName, forPerson: person)
})
// Perform change
person.name = name
// ...
}
Run Code Online (Sandbox Code Playgroud)
警告
如果您发现您的撤消不(即,它执行了,但没有什么似乎已经发生了,就好像撤消操作运行,但它仍然显示你想撤消值从),仔细考虑一下值(旧名在上面的例子中)实际上是在执行撤销处理程序闭包时.
您想要还原的任何旧值(如oldName本示例中所示)必须在捕获列表中捕获.也就是说,如果上面例子中的闭包的单行是:
target.setName(person.name, forPerson: person)
Run Code Online (Sandbox Code Playgroud)
...撤消不起作用,因为在执行撤销处理程序关闭时,person.name设置为新名称,这意味着当用户执行撤消时,您的应用程序(在上面的简单情况下)似乎什么都不做,因为它是将名称设置为其当前值,这当然不会撤消任何内容.
[oldName = person.name]在signature ()之前的捕获列表()(MyViewController) -> ()声明oldName在声明闭包时引用person.name它,而不是在它被执行时引用.
有关捕获列表的更多信息
有关捕获列表的更多信息,Erica Sadun撰写了一篇名为Swift的文章:在闭包中捕获引用.同样值得关注她提到的保留周期问题.此外,虽然她在她的文章中没有提到它,但我在上面使用它时捕获列表中的内联声明来自Swift编程语言的Swift 2.0的表达式部分.
其他方法
当然,更简洁的方法是let oldName = person.name在你的调用之前registerUndoWithTarget(_:handler:),然后oldName在范围内自动捕获.我发现捕获列表方法更容易阅读,因为它与处理程序一致.
我也完全没能registerWithInvocationTarget()与非NSObject类型(如Swift enum)作为参数玩得很好.在后一种情况下,请记住,不仅应该从调用目标继承NSObject,而且还要调用在该调用目标上调用的函数的参数.或至少是类型的桥可可类型(如String和NSString或Int和NSNumber,等等).但是也存在一些问题,即调用目标没有被保留,我无法解决.此外,使用闭包作为完成处理程序更加迅速.
在结束时(得到它?)
弄清楚所有这些让我花费了几个小时的勉强控制的愤怒(可能我的Apple Watch关于我的心率有些担忧 - " 点击水龙头!老兄......一直在听你的心脏,你可能想要冥想或者其他的东西").我希望我的痛苦和牺牲有所帮助.:-)
更新2:Xcode 6.1中的Swift已经undoManager可选,因此您可以像这样调用prepareWithInvocationTarget():
undoManager?.prepareWithInvocationTarget(myTarget).insertSomething(someObject, atIndex: index)
Run Code Online (Sandbox Code Playgroud)
更新:Swift在Xcode6 beta5中简化了undo manager的prepareWithInvocationTarget()的使用.
undoManager.prepareWithInvocationTarget(myTarget).insertSomething(someObject, atIndex: index)
Run Code Online (Sandbox Code Playgroud)
以下是beta4所需的内容:
基于NSInvocation的撤消管理器API仍可以使用,虽然它不是在第一个明显的如何调用它.我使用以下方法计算出如何成功调用它:
let undoTarget = undoManager.prepareWithInvocationTarget(myTarget) as MyTargetClass?
undoTarget?.insertSomething(someObject, atIndex: index)
Run Code Online (Sandbox Code Playgroud)
具体来说,您需要将结果prepareWithInvocationTarget()转换为目标类型,但请记住将其设置为可选,否则会导致崩溃(无论如何都会在beta4上).然后,您可以使用要在撤消堆栈上记录的调用来调用您键入的可选项.
还要确保您的调用目标类型继承自NSObject.
我在操场上尝试过,效果完美:
class UndoResponder: NSObject {
@objc func myMethod() {
print("Undone")
}
}
var undoResponder = UndoResponder()
var undoManager = UndoManager()
undoManager.registerUndo(withTarget: undoResponder, selector: #selector(UndoResponder.myMethod), object: nil)
undoManager.undo()
Run Code Online (Sandbox Code Playgroud)