在绘图应用程序Swift 3中使用UndoManager实现重做和撤消

时间:2016-09-26 23:07:06

标签: ios swift nsundomanager

我正在开发一个包含注释工具的项目,该工具允许用户绘制"用手指手势或铅笔的文件。当然,我热衷于为绘制路径实现撤消/重做。

我对绘图应用程序的实现是相对传统的。用户在屏幕上看到的是缓存的位图图像(在当前之前绘制的所有路径的快照)与" live"渲染当前路径(UIBezierPath)。触发touchesEnded时,新路径将添加到位图。

我已经能够以相对较小的麻烦实现撤销。我为类创建了一个标准的undoManager:

let myUndoManager : UndoManager = {
    let mUM : UndoManager = UndoManager()
    mUM.levelsOfUndo = 6
    return mUM
}()

在touchesEnded结束时调用以渲染新缓存路径的函数称为drawBitmap。在此函数开始时,假设存在先前的缓存路径,并且在绘制新路径之前,我使用撤消管理器注册以下撤消操作:

let previousCachedPath : UIImage = self.cachedPath
self.myUndoManager.registerUndo(withTarget: self, selector: #selector(self.setBitmap(_:)), object: previousCachedPath)

setBitmap(_ previousCachedPath:UIImage)是一个将显示的位图重置为提供的图像的函数。

我有一个undo / redo按钮,分别链接到undo()和redo()方法。除了某些逻辑指示何时应该激活这些按钮(即确保在没有绘制任何内容时无法按下撤销等)时,这些按钮分别只调用myUndoManager.undo()和myUndoManager.redo(): / p>

func undo() -> Void {
    guard self.myUndoManager.canUndo else { return }
    self.myUndoManager.undo()
    if !self.redoButton.isEnabled {
        self.redoButton.isEnabled = true
    }
    if !self.myUndoManager.canUndo {
        self.undoButton.isEnabled = false
    }

    self.setNeedsDisplay()
}

func redo() -> Void {
    guard self.myUndoManager.canRedo else { return }
    self.myUndoManager.redo()
    if !self.undoButton.isEnabled {
        self.undoButton.isEnabled = true
    }
    if !self.myUndoManager.canRedo {
        self.redoButton.isEnabled = false
    }

    self.setNeedsDisplay()
}

正如我所提到的,undo完全符合指定的六级可撤销性。但是,我明显错过了重做的东西。我最初的希望是当调用undo时,undoManager会自动将撤销任务从撤销堆栈转移到重做堆栈,但这显然不会发生。

我已经搜索了答案,我认为最接近我需要的可能是按照以下方式使用相互递归:

Using NSUndoManager, how to register undos using Swift closures

但是,我无法完成这项工作。任何帮助因此赞赏!

2 个答案:

答案 0 :(得分:4)

感谢@matt的帮助,我通过将所有内容放在setBitmap(_ :)函数中来解决这个问题。为了更好地理解事物,我实现了registerUndo(withTarget:selector :)方法:

func setBitmap(_ toCachedPath : UIImage) -> Void {
    self.myUndoManager.registerUndo(withTarget: self, selector: #selector(self.setBitmap(_:)), object: self.cachedPath)

    UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0)
    toCachedPath.draw(at: CGPoint.zero)     
    self.cachedPath = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
}

还有prepare(withInvocationTarget :)方法:

func setBitmap(_ toCachedPath : UIImage) -> Void {
    if self.cachedPath != nil {
        (self.rWUndoManager.prepare(withInvocationTarget: self) as AnyObject).setBitmap(self.cachedPath)
    }

    UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0)
    toCachedPath.draw(at: CGPoint.zero)
    self.cachedPath = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
}

希望能帮助那些像我一样摸不着头脑的人。

答案 1 :(得分:0)

实际上你可以使用闭包版本registerUndo(withTarget:handler:)进行重做。只需确保它首先与选择器registerUndo(withTarget:selector:object:)一起使用,即创建一个只接受一个参数的函数,就像你的答案一样。然后你可以用基于闭包的方法替换基于选择器的方法:

func setBitmap(_ toCachedPath : UIImage) -> Void {
    let oldCachedPath = cachedPath // For not referencing it with `self` in the closure.
    myUndoManager.registerUndo(withTarget: self) {
        $0.setBitmap(oldCachedPath)
    }

    UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0)
    toCachedPath.draw(at: CGPoint.zero)     
    self.cachedPath = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
}

我认为闭包版本只是一个重新打包的选择器版本,它只能识别一个方法和一个输入参数,所以我们仍然必须这样写它才能工作。