【问题标题】:Implementing redo alongside undo with UndoManager in drawing application Swift 3在绘图应用程序 Swift 3 中使用 UndoManager 实现重做和撤消
【发布时间】:2016-09-26 23:07:06
【问题描述】:

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

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

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

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

在 touchesEnded 结束时调用的用于渲染新缓存路径的函数称为 drawBitmap。在这个函数开始时,假设有一个以前的缓存路径,在绘制新路径之前,我向 undo manager 注册了以下 undo action:

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

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

我有一个分别与 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()
}

正如我所提到的,撤消对指定的六个可撤消级别非常有效。但是,我显然缺少重做的东西。我最初的希望是 undoManager 会在调用 undo 时自动将 undo 任务从 undo 堆栈转移到 redo 堆栈,但这显然不会发生。

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

Using NSUndoManager, how to register undos using Swift closures

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

【问题讨论】:

  • 诀窍是你应该只有 一个 方法,撤消和重做都应该调用它。重做的撤消是撤消。撤消的撤消是重做。它可能会帮助你阅读我的书章节:apeth.com/iOSBook/ch39.html
  • 谢谢 - 今天早上我会读一读,让你知道我的进展如何!
  • @matt 非常感谢,工作完美。正如您所建议的,我将所有内容组合成一种方法,在本例中为 setBitmap(_:)。我让它与您概述的任何一种方法一起工作。我已经发布了我的解决方案,以防它帮助其他人。

标签: ios swift nsundomanager


【解决方案1】:

感谢@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()
}

希望对其他和我一样摸不着头脑的人有所帮助。

【讨论】:

    【解决方案2】:

    实际上,您也可以使用闭包版本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()
    }
    

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

    【讨论】:

      猜你喜欢
      • 2013-01-27
      • 2015-07-28
      • 1970-01-01
      • 1970-01-01
      • 2011-04-03
      • 2013-08-20
      • 2013-01-26
      • 2015-03-30
      • 1970-01-01
      相关资源
      最近更新 更多