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

I am migrating a Mac OS X Xode 7 / Swift 2.2 project to Xcode 8 / Swift 3 and I am having a problem using undoManager in my controller class MyViewController, which has a cancel function.

On 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 template 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 for MyViewController always fails, and the undo operation is not registered.

Am I missing something obvious here, or is this a mistake?

+5
source share
3 answers

prepareWithInvocationTarget(_:) (or prepare(withInvocationTarget:) in Swift 3) creates a hidden proxy object that the Swift 3 runtime cannot work well with.

(You can call it a bug and send a bug report.)

To achieve your goal, you cannot use registerUndo(withTarget:handler:) ?

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

I had the same problem and was not ready to give up support for iOS 8 and macOS 10.10 or return to Swift 2.3. The syntax of registerUndo(withTarget:handler) is good, so I just simply flipped my own version:

 /// 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() } } 

Hope this helps someone else too.

+2
source

Backward compatible solution with OS 10.10: use registerUndo(with Target: selector: object: ) . No problem to keep one value. To save several values, I pack them into a dictionary and use the "object" parameter. To cancel the operation, I will unpack them from the dictionary, and then I will call the OS10.11 + undo method with these values.

+1
source

All Articles