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

时间:2016-04-11 18:04:31

标签: ios swift core-graphics

我正在尝试在Swift 2中创建一个绘图应用程序,并且在尝试实现“撤消”按钮时撞墙。我对此非常陌生(实际上是非常新的),并且我已尽力解决这个问题,但我发现的所有其他示例都是使用不同的版本或语言,或者只是不适用..也许正是我缺乏知识阻碍了我在这里的进步。

我尝试过使用'undoManager',但我不知道如何使用它。我曾尝试阅读可用的在线指南,但仍然无能为力!

这是我的按钮:

@IBAction func undoDrawing(sender: AnyObject) {
}

我已设置touchesBegan

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

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

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

所以我只需要一种方法来扭转这些'触动'所采取的行动。有什么想法吗?

1 个答案:

答案 0 :(得分:4)

我可以想到两种方法,轻松和放松。有限的方式和艰难的概括的方式。

简单方法

最简单的方法是只允许撤消单个更改,即在内存中维护图像的第二个副本。在伪代码中,

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

// Revert currentImage to most-recent backupImage
undo {
    currentImage = backupImage
}

这类似于方案used by the original 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

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

然后,您的撤消功能将是

undo(image) {
    scale(image, 1 / scaleHistory.lastObject())
    scaleHistory.removeLastObject()
}

从上面继续执行,

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

现在scaleHistory为空,您应该停用undo按钮!

这是NSUndoManager所期望的范式;请参阅this article,其中介绍了撤消堆栈的使用。

如何在您的应用中发挥作用

我看到你实现这个范例的一种方法是直接替换图像上的绘图,并在数据结构中记录绘图操作的意图。因此,您将有一个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
}

至关重要的是,您必须在每次调用canvas时重置draw(),以便确保lines中的每一行都只呈现一次。

当然,这与缩放应用程序并不完全相似,因为undo()并不是简单地应用最后一个操作的反转。相反,我们重置屏幕上的图像 - 相当于撤消所有操作 - 然后选择性地重新执行我们想要保留的 draw()函数