在Swift 2中的绘图应用程序中创建“撤消”功能

Chr*_*bin 2 core-graphics ios swift

我正在尝试在Swift 2中创建绘图应用程序,并且在尝试实现“撤消”按钮时碰到了墙。我对此很陌生(实际上是非常新的),并且尽了最大的努力弄清楚了,但是我发现的所有其他示例都使用不同的版本或语言,或者根本不适用。也许是由于我缺乏知识阻碍了我在这里的进步。

我已经尝试过使用“ undoManager”,但是我不知道如何使用它。我尝试阅读可用的在线指南,但仍然一无所知!

这是我的按钮:

@IBAction func undoDrawing(sender: AnyObject) {
}
Run Code Online (Sandbox Code Playgroud)

我已经建立了touchesBegan

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?){
        isSwiping    = false
    if let touch = touches.first{
        lastPoint = touch.locationInView(imageView)
    }
}
Run Code Online (Sandbox Code Playgroud)

touchesMoved

override func touchesMoved(touches: Set<UITouch>,
                           withEvent event: UIEvent?){
    isSwiping = true;
    if let touch = touches.first{

        let currentPoint = touch.locationInView(imageView)
        UIGraphicsBeginImageContext(self.imageView.frame.size)
        self.imageView.image?.drawInRect(CGRectMake(0, 0, self.imageView.frame.size.width, self.imageView.frame.size.height))
        CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y)
        CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y)
        CGContextSetLineCap(UIGraphicsGetCurrentContext(),CGLineCap.Round)
        CGContextSetLineWidth(UIGraphicsGetCurrentContext(), myCGFloat)
        CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(), self.selectedColor.CGColor)
        CGContextStrokePath(UIGraphicsGetCurrentContext())
        self.imageView.image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        lastPoint = currentPoint
    }
} 
Run Code Online (Sandbox Code Playgroud)

touchesEnded

override func touchesEnded(touches: Set<UITouch>,
                           withEvent event: UIEvent?){

    if(!isSwiping) {

        UIGraphicsBeginImageContext(self.imageView.frame.size)
        self.imageView.image?.drawInRect(CGRectMake(0, 0, self.imageView.frame.size.width, self.imageView.frame.size.height))
        CGContextSetLineCap(UIGraphicsGetCurrentContext(), CGLineCap.Round)
        CGContextSetLineWidth(UIGraphicsGetCurrentContext(), myCGFloat)
        CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(), self.selectedColor.CGColor)
        CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y)
        CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y)
        CGContextStrokePath(UIGraphicsGetCurrentContext())
        self.imageView.image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,我所需要的只是一种方法,可以逆转这些“接触”所采取的动作。有任何想法吗?

aed*_*ols 5

我可以想到两种解决方法,简单限制方法和硬性概括方法。


简单的方法

最简单的方法(仅允许撤消单个更改)是在内存中维护映像的第二个副本。用伪代码,

// Update contents of backupImage, draw new line on currentImage
drawNewLine {
    backupImage = currentImage 
    drawLineOnCurrentImage()
}

// Revert currentImage to most-recent backupImage
undo {
    currentImage = backupImage
}
Run Code Online (Sandbox Code Playgroud)

这类似于原始MacPaint使用的方案(请参阅“问题3:如何撤消?”)。

此方法不允许重做,也不需要(或不允许使用)NSUndoManager


艰辛的道路

对于更通用的解决方案,您需要弄清楚如何将每个绘图事件表示为可逆函数。

原始示例应用

想象一下,绘图应用程序支持的唯一操作是缩放。从而,

scale(image, 2.0)   // push 2.0 onto stack - current size = 2.0x
scale(image, 3.0)   // push 3.0 onto stack - current size = 6.0x
Run Code Online (Sandbox Code Playgroud)

返回的图像是大小的六倍。您可以使用一堆浮点数来表示此操作,例如scaleHistory = [2.0, 3.0]

然后,您的撤消功能将是

undo(image) {
    scale(image, 1 / scaleHistory.lastObject())
    scaleHistory.removeLastObject()
}
Run Code Online (Sandbox Code Playgroud)

从上方继续执行,

undo(image) // Pop 3.0 from the stack. Scale by 1 / 3.0, new size = 2.0x
undo(image) // Pop 2.0 from the stack. Scale by 1 / 2.0, new size = 1.0x
Run Code Online (Sandbox Code Playgroud)

现在scaleHistory是空的,您应该禁用该undo按钮!

这是期望的范式NSUndoManager; 请参阅本文,其中介绍了撤消堆栈的用法。

如何在您的应用程序中完成这项工作

我看到的一种实现此范例的方法是,用数据结构中记录绘图动作的意图直接替换图像上的绘图。因此,您将有一个CGPoint成对的向量,代表用户添加的行。用伪代码,

lines = []

addLine(start, finish) {
    lines.addObject((start, finish))
    draw()
}

draw() {
    canvas = nil
    foreach (lines as line) {
        canvas.paintToScreen(line)  // this function invokes CGContextMoveToPoint, CGContextAddLineToPoint, etc.
    }
}

undo() {
    lines.removeLastObject()
    draw()
}

canUndo() {
    return lines.count > 0
}
Run Code Online (Sandbox Code Playgroud)

至关重要的是,您必须canvas在每次调用时重设您的位置,draw()以确保输入的每一行都lines被精确渲染一次。

当然,这并不是缩放应用程序的精确模拟,因为undo()它不只是应用最后一个操作的逆函数。相反,我们重置屏幕上的图像- 等同于撤消所有操作 -然后有选择地重新执行我们要保留draw()功能中的图像。