问题
I am migrating an Xcode 7 / Swift 2.2 mac OS X project to Xcode 8 / Swift 3, and I have run into a problem using undoManager in my view controller class, MyViewController, which has a function undo.
In Xcode 7 / Swift 2.2, this worked fine:
undoManager?.prepareWithInvocationTarget(self).undo(data, moreData: moreData)
undoManager?.setActionName("Change Data)
In Xcode 8 / Swift 3, using the recommended pattern from https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html
this should be changed to:
if let target = undoManager?.prepare(withInvocationTarget: self) as? MyViewController {
target.undo(data, moreData: moreData)
undoManager?. setActionName("Change Data")
}
However, the downcast to MyViewController always fails, and the undo operation is not registered.
Am I missing something obvious here, or is this a bug?
回答1:
prepareWithInvocationTarget(_:)
(or prepare(withInvocationTarget:)
in Swift 3) creates a hidden proxy object, with which Swift 3 runtime cannot work well.
(You may call that a bug, and send a bug report.)
To achieve your purpose, can't you use registerUndo(withTarget:handler:)
?
undoManager?.registerUndo(withTarget: self) {targetSelf in
targetSelf.undo(data, moreData: moreData)
}
回答2:
I've had the same issue and I wasn't prepared to drop iOS 8 and macOS 10.10 support or go back to Swift 2.3. The registerUndo(withTarget:handler)
syntax is nice though, so I basically just rolled my own version of that:
/// 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()
}
}
Hopefully it's helpful to someone else too.
回答3:
Solution for backward compatibility with OS 10.10: use registerUndo(with Target: selector: object: )
. No problem for saving single value. To save multiple values, I pack them into a dictionary and use that for the "object" parameter. For the undo operation, I unpack them from the dictionary, and then call the OS10.11+ undo method with those values.
来源:https://stackoverflow.com/questions/39562636/using-nsundomanager-and-preparewithinvocationtarget-in-swift-3