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

3

8

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?

Changchangaris answered 18/9, 2016 at 21:1 Comment(0)
S
11

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)
}
Southeastwardly answered 18/9, 2016 at 23:44 Comment(4)
Thanks. I have played around with that as well. Unfortunately it is not compatible with OS10.10, but maybe its time to move on.Changchangaris
Solution for 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.Changchangaris
@jbaraga, thanks for reporting. But why won't you post it as another answer? It may be some help for developers who needs to work with 10.10.Southeastwardly
Good suggestion. Done.Changchangaris
S
3

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.

Sepulchral answered 26/10, 2016 at 8:38 Comment(0)
C
2

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.

Changchangaris answered 23/9, 2016 at 21:38 Comment(1)
Thanks. With including "multiple values" case code, more people can easily find your answer is useful. Please consider taking some time for it.Southeastwardly

© 2022 - 2024 — McMap. All rights reserved.