在 Swift 3 中使用 NSUndoManager 和 .prepare(withInvocationTarget:)

Using NSUndoManager and .prepare(withInvocationTarget:) in Swift 3

我正在将 Xcode 7 / Swift 2.2 mac OS X 项目迁移到 Xcode 8 / Swift 3,并且我 运行 在我的视图控制器 class、MyViewController 中使用 undoManager 时遇到问题,它具有撤消功能。

在 Xcode 7 / Swift 2.2 中,这工作正常:

undoManager?.prepareWithInvocationTarget(self).undo(data, moreData: moreData)
undoManager?.setActionName("Change Data)

在 Xcode 8 / Swift 3 中,使用 https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html

中的推荐模式

应改为:

if let target = undoManager?.prepare(withInvocationTarget: self) as? MyViewController {
    target.undo(data, moreData: moreData)
    undoManager?. setActionName("Change Data")
}

但是downcast到MyViewController总是失败,undo操作没有注册

我是不是遗漏了一些明显的东西,或者这是一个错误?

prepareWithInvocationTarget(_:)(或 Swift 3 中的 prepare(withInvocationTarget:))创建了一个隐藏的代理对象,Swift 3 运行时无法正常工作。

(您可以称其为错误,并发送错误报告。)

为了达到你的目的,你不能用registerUndo(withTarget:handler:)吗?

undoManager?.registerUndo(withTarget: self) {targetSelf in
    targetSelf.undo(data, moreData: moreData)
}

向后兼容 OS 10.10 的解决方案:使用 registerUndo(with Target: selector: object: )。保存单个值没问题。为了保存多个值,我将它们打包到字典中并将其用于 "object" 参数。对于撤消操作,我从字典中解压它们,然后使用这些值调用 OS10.11+ 撤消方法。

我遇到了同样的问题,我不准备放弃 iOS 8 和 macOS 10.10 支持或返回到 Swift 2.3。 registerUndo(withTarget:handler) 语法很好,所以我基本上只是推出了我自己的版本:

/// An extension to undo manager that adds closure based
/// handling to OS versions where it is not available.
extension UndoManager
{
    /// Registers an undo operation using a closure. Behaves in the same wasy as 
    /// `registerUndo(withTarget:handler)` but it compatible with older OS versions.
    func compatibleRegisterUndo<TargetType : AnyObject>(withTarget target: TargetType, handler: @escaping (TargetType) -> ())
    {
        if #available(iOS 9.0, macOS 10.11, *)
        {
            self.registerUndo(withTarget: target, handler: handler)
        }
        else
        {
            let operation = BlockOperation {
                handler(target)
            }
            self.registerUndo(withTarget: self, selector: #selector(UndoManager.performUndo(operation:)), object: operation)
        }
    }

    /// Performs an undo operation after it has been registered
    /// by `compatibleRegisterUndo`. Should not be called directly.
    func performUndo(operation: Operation)
    {
        operation.start()
    }
}

希望对其他人也有帮助。