I'm having trouble adopting the more complex invocation-based approach to undo registration in Swift (based on NSHipster article here. Apple's docs still have all sample code in Objective-C, and the semantics are very different for the invocation setup).
My NSDocument
subclass Document
has the following method that operates on the model objects, which I wish to make undoable:
func rename(object: Any, to newName: String) {
// This is basically a protocol that requires implementing:
// var name: String { get set }
//
guard var namedObject = object as? EditorHierarchyDisplayable else {
return
}
// Register undo:
let undoController = undoManager?.prepare(withInvocationTarget: self) as? Document
undoController?.rename(object: namedObject, to: namedObject.name)
undoManager?.setActionName("Rename \(namedObject.localizedClassName)")
// Perform the change:
namedObject.name = newName
}
What I have found out is that undoController
above is nil
, becuase the atempted cast to Document
fails. If I remove the cast (and comment out the call to undoController.rename(...
), prepare(withInvocationTarget:)
returns the folowing object:
(lldb) print undoController
(Any?) $R0 = some {
payload_data_0 = 0x00006080000398a0
payload_data_1 = 0x0000000000000000
payload_data_2 = 0x0000000000000000
instance_type = 0x000060800024f0d8
}
(lldb) print undoController.debugDescription
(String) $R1 = "Optional(NSUndoManagerProxy)"
(lldb)
What am I missing?
I think the basic confusion is that prepare(withInvocationTarget:)
returns a proxy object (that happens to be the undo manager itself, but that's an implementation detail). The idea is that you send this proxy object the same message(s) you send to undo the action, but instead of executing them (because it's not the actual object), it internally captures those invocations and saves them for later.
So your code should really start out something like this:
let selfProxy: Any = undoManager?.prepare(withInvocationTarget: self)
This works great in Objective-C because the "catchall" type (id) has very lax type checking. But the equivalent Any
class in Swift is much more stringent and does not lend itself to the same technique, if at all.
See Using NSUndoManager and .prepare(withInvocationTarget:) in Swift 3