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

Ios 在绘图应用程序Swift 3中使用UndoManager实现重做和撤消,ios,swift,nsundomanager,Ios,Swift,Nsundomanager,我正在做一个项目,其中包括一个注释工具,允许用户用手指或铅笔在文档上“画图”。当然,我热衷于为绘制的路径实现撤销/重做 我对绘图应用程序的实现相对比较传统。用户在屏幕上看到的是缓存位图图像(在当前路径之前绘制的所有路径的快照)与当前路径的“实时”呈现(UIBezierPath)的组合。触发touchesEnded时,新路径将添加到位图中 我已经能够以相对较少的麻烦实现撤销。我已经为该类创建了一个标准管理器: let myUndoManager : UndoManager = { let

我正在做一个项目,其中包括一个注释工具,允许用户用手指或铅笔在文档上“画图”。当然,我热衷于为绘制的路径实现撤销/重做

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

我已经能够以相对较少的麻烦实现撤销。我已经为该类创建了一个标准管理器:

let myUndoManager : UndoManager = {
    let mUM : UndoManager = UndoManager()
    mUM.levelsOfUndo = 6
    return mUM
}()
在touchesEnded结束时调用以呈现新缓存路径的函数称为drawBitmap。在该函数开始时,假设存在以前的缓存路径,并且在绘制新路径之前,我向undo manager注册以下undo操作:

let previousCachedPath : UIImage = self.cachedPath
self.myUndoManager.registerUndo(withTarget: self, selector: #selector(self.setBitmap(_:)), object: previousCachedPath)
setBitmap(uPreviousCachedPath:UIImage)是一个函数,用于将显示的位图重置为提供的图像

我有一个undo/redo按钮,分别链接到undo()和redo()方法。除了一些逻辑指示这些按钮何时应处于活动状态(即确保在未绘制任何内容时不能按撤消等),这些按钮只需分别调用myUndoManager.undo()和myUndoManager.redo():

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时,undoManager会自动将undo任务从undo堆栈转移到redo堆栈,但这显然没有发生

我已经搜索了答案,我认为最接近我需要的可能是使用相互递归,如下所示:


然而,我一直无法做到这一点。因此,感谢您的帮助

多亏@matt的帮助,我把所有的东西都放在setBitmap(:)函数中,解决了这个问题。为了尝试更好地理解事物,我实现了registerUndo(with target: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()
}
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()
}
还有prepare(withInvocationTarget:)方法:

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

希望这能帮助其他像我一样挠头的人。

实际上,您可以使用闭包版本
registerUndo(带target:handler:)
来实现重做。只需确保它首先与选择器one
registerUndo(with target: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()
}

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

诀窍在于,您应该只有一个方法,并且undo和redo都应该调用它。重做的撤销就是撤销。撤销的撤销是重做。这可能会帮助你阅读我的书章节:谢谢-今天早上我会读一读,让你知道我的进展如何@马特非常感谢,做得很好。正如您所建议的,我将所有内容合并到一个方法中,在本例中是setBitmap(:)。我用你概述的两种方法中的任何一种都能做到。我已经发布了我的解决方案,以防它对其他人有帮助。