使用Cglayer绘图进行撤消和重做

我正在使用绘图应用程序,我正在使用CGlayers进行绘图。 触摸结束后,我从图层中取出图像并将其存储在一个Array中,我使用该Array来撤消操作。

我的触摸结束function

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@"Touches ended"); UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextDrawLayerInRect(context, self.bounds, self.drawingLayer); m_curImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); [m_undoArray addObject: m_curImage]; } 

我的绘图视图根据用户需求dynamic扩展,所以假设用户可以绘制一个drawView大小为200 * 200的线,然后将其展开为200 * 300并再绘制一行,然后将其展开为200 * 300并再绘制一行。

这是应用程序的图像

所以现在我有3个不同大小的图像在UndoArray。

每当我增加/减lesscanvas大小。 我已经写了这个代码

在drawingView的增加和减less,我正在写这个function

  (void)increaseDecreaseDrawingView { self.currentDrawingLayer = nil; if(self.permanentDrawingLayer) { rectSize = self.bounds; NSLog(@"Size%@", NSStringFromCGRect(self.bounds)); CGContextRef context = UIGraphicsGetCurrentContext(); //self.newDrawingLayer = CGLayerCreateWithContext(context, self.bounds.size, NULL); CGFloat scale = self.contentScaleFactor; CGRect bounds = CGRectMake(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale); CGLayerRef layer = CGLayerCreateWithContext(context, bounds.size, NULL); CGContextRef layerContext = CGLayerGetContext(layer); CGContextScaleCTM(layerContext, scale, scale); self.newDrawingLayer = layer; CGContextDrawLayerInRect(layerContext, self.bounds, self.permanentDrawingLayer ); self.permanentDrawingLayer = nil; } 

而为了做撤销,我写了这个代码

 - (void)Undo { //Destroy the layer and create it once again with the image you get from undoArray. self.currentDrawingLayer = Nil; CGContextRef layerContext1 = CGLayerGetContext(self.permanentDrawingLayer ); CGContextClearRect(layerContext1, self.bounds); CGContextRef context = UIGraphicsGetCurrentContext(); for(int i =0; i<[m_rectArrayUndo count];i++) { CGRect rect = [[m_rectArrayUndo objectAtIndex:i]CGRectValue]; CGLayerRef undoLayer = CGLayerCreateWithContext(context, rect.size, NULL); CGContextRef layerContext = CGLayerGetContext(undoLayer ); CGContextTranslateCTM(layerContext, 0.0, rect.size.height); CGContextScaleCTM(layerContext, 1.0, -1.0); CGRect imageFrame; NSDictionary *lineInfo = [m_undoArray objectAtIndex:i]; m_curImage = [lineInfo valueForKey:@"IMAGE"]; imageFrame = CGRectMake(0 ,0,m_curImage.size.width,m_curImage.size.height); CGContextDrawImage(layerContext, imageFrame, m_curImage.CGImage); CGContextDrawLayerInRect(context, rect, undoLayer ); CGContextDrawLayerInRect(layerContext1, rect, undoLayer); } } 

在我的drawRect函数中,我写了这个代码

 - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext();//Get a reference to current context(The context to draw) if(self.currentDrawingLayer == nil) { CGLayerRef layer = CGLayerCreateWithContext(context, bounds.size, NULL); self.currentDrawingLayer = layer; } if(self.permanentDrawingLayer == nil) { CGLayerRef layer = CGLayerCreateWithContext(context, bounds.size, NULL); self.permanentDrawingLayer = layer; } if(self.newDrawingLayer == nil) { CGLayerRef layer = CGLayerCreateWithContext(context, bounds.size, NULL); self.newDrawingLayer = layer; } CGPoint mid1 = midPoint(m_previousPoint1, m_previousPoint2); CGPoint mid2 = midPoint(m_currentPoint, m_previousPoint1); CGContextRef layerContext = CGLayerGetContext(self.currentDrawingLayer); CGContextSetLineCap(layerContext, kCGLineCapRound); CGContextSetBlendMode(layerContext, kCGBlendModeNormal); CGContextSetLineJoin(layerContext, kCGLineJoinRound); CGContextSetLineWidth(layerContext, self.lineWidth); CGContextSetStrokeColorWithColor(layerContext, self.lineColor.CGColor); CGContextSetShouldAntialias(layerContext, YES); CGContextSetAllowsAntialiasing(layerContext, YES); CGContextSetAlpha(layerContext, self.lineAlpha); CGContextSetFlatness(layerContext, 1.0f); CGContextBeginPath(layerContext); CGContextMoveToPoint(layerContext, mid1.x, mid1.y);//Position the current point CGContextAddQuadCurveToPoint(layerContext, m_previousPoint1.x, m_previousPoint1.y, mid2.x, mid2.y); CGContextStrokePath(layerContext);//paints(fills) the line along the current path. CGContextDrawLayerInRect(context, self.bounds, self.newDrawingLayer); CGContextDrawLayerInRect(context, self.bounds, self.permanentDrawingLayer); CGContextDrawLayerInRect(context, self.bounds, self.currentDrawingLayer); [super drawRect:rect]; } 

我有一点怀疑

  1. 这是正确的方法吗? 或者是他们的更好的方法。

  2. 这里发生的是,我的图像从撤消arrays不尊重rects,并在新的图层上的任何随机位置绘制。

所以我想知道如何正确绘制它们,以便在特定位置的CGlayers上正确绘制图像。

首先,因为你正在使用图层,所以我build议放弃drawRect:并且只使用CALayer变换。

其次,在我看来,实现撤销 – 重做操作的最好方式将始终是基于命令的。 作为一个非常简单的例子,您可以为每个命令创build单独的方法:

 - (void)scaleLayerBy:(CGFloat)scale; - (void)moveLayerByX:(CGFloat)x Y:(CGFloat)y; // etc 

然后每次用户做一个动作,你添加到一个NSMutableArray的动作ID和参数:

 [self.actionHistory addObject:@{ @"action": @"move", @"args": @[@10.0f, @20.0f] }]; 

相反,如果用户调用undo ,则删除该数组中的最后一个对象。

然后,当你需要重新加载显示,只需重新评估数组中的所有命令。

 [self resetLayers]; // reset CALayers to their initial state for (NSDictionary *command in self.actionHistory) { NSArray *arguments = command[@"args"]; if ([command[@"action"] isEqualToString:@"move"]) { [self moveLayerByX:[arguments[0] floatValue] Y:[arguments[1] floatValue]]; } // else if other commands } 

每个触摸事件的图像对象是一个糟糕的主意恕我直言,你撕裂通过公羊。 为什么不保存一系列触点并dynamic绘制? 足够简单的去除该arrays中的最后几个元素以进行便宜的撤销操作

//// 2014年1月14日// //编辑以包含示例//

确定这里是一个快速绘图视图的例子。 有三个mutableArrays,_touches,这是所有以前的图纸,_currentTouch,这是当前的graphics,只包含触摸事件期间,(触摸开始和触摸结束之间)数据和重做数组的数据,通过撤消被复制而不是只是删除它(你当然可以做)

请享用 :)

 // // JEFdrawingViewExample.m // Created by Jef Long on 14/01/2014. // Copyright (c) 2014 Jef Long / Dragon Ranch. All rights reserved. // #import "JEFdrawingViewExample.h" ///don't worry, the header is empty :) /// this is a subclass of UIView @interface JEFdrawingViewExample() -(UIColor *)colourForLineAtIndex:(int)lineIndex; //swaps the coulour for each line -(void)undo; -(void)redo; @end; @implementation JEFdrawingViewExample { //iVars NSMutableArray *_touches; NSMutableArray *_currentTouch; NSMutableArray *_redoStore; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // Initialization code _touches = [[NSMutableArray alloc]init]; _currentTouch = [[NSMutableArray alloc]init]; _redoStore = [[NSMutableArray alloc]init]; } return self; } #pragma mark - touches -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ UITouch *touch = [touches anyObject]; CGPoint touchPoint = [touch locationInView:self]; [_currentTouch removeAllObjects]; [_currentTouch addObject:NSStringFromCGPoint(touchPoint)]; ///there are other, possibly less expensive ways to do this.. (adding a CGPoint to an NSArray.) // typecasting to (id) doesnt work under ARC.. // two NSNumbers probably not any cheaper.. } -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ UITouch *touch = [touches anyObject]; CGPoint touchPoint = [touch locationInView:self]; [_currentTouch addObject:NSStringFromCGPoint(touchPoint)]; [self setNeedsDisplay]; } -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{ UITouch *touch = [touches anyObject]; CGPoint touchPoint = [touch locationInView:self]; [_currentTouch addObject:NSStringFromCGPoint(touchPoint)]; [_touches addObject:[NSArray arrayWithArray:_currentTouch]]; [_currentTouch removeAllObjects]; [self setNeedsDisplay]; } -(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{ [_currentTouch removeAllObjects]; [self setNeedsDisplay]; } #pragma mark - drawing - (void)drawRect:(CGRect)rect { //we could be adding a CALayer for each new line, which would be cheaper because you could draw each and basically forget it CGContextRef _context = UIGraphicsGetCurrentContext(); CGContextSetLineWidth(_context, 1.0); //or whatever ///older lines if ([_touches count]) { for (int line = 0; line < [_touches count]; line ++) { NSArray *thisLine = [_touches objectAtIndex:line]; if ([thisLine count]) { CGContextSetStrokeColorWithColor(_context, [self colourForLineAtIndex:line].CGColor); CGPoint start = CGPointFromString([thisLine objectAtIndex:0]); CGContextMoveToPoint(_context, start.x, start.y); for (int touch = 1; touch < [thisLine count]; touch ++) { CGPoint pt = CGPointFromString([thisLine objectAtIndex:touch]); CGContextAddLineToPoint(_context, pt.x, pt.y); } CGContextStrokePath(_context); } } } ///current line if ([_currentTouch count]) { CGPoint start = CGPointFromString([_currentTouch objectAtIndex:0]); CGContextSetStrokeColorWithColor(_context, [self colourForLineAtIndex:[_touches count]].CGColor); CGContextMoveToPoint(_context, start.x, start.y); for (int touch = 1; touch < [_currentTouch count]; touch ++) { CGPoint touchPoint = CGPointFromString([_currentTouch objectAtIndex:touch]); CGContextAddLineToPoint(_context, touchPoint.x, touchPoint.y); } CGContextStrokePath(_context); } } -(UIColor *)colourForLineAtIndex:(int)lineIndex{ return (lineIndex%2 == 0) ? [UIColor yellowColor] : [UIColor purpleColor]; /// you might have a diff colour for each line, eg user might select a pencil from a toolbar etc } #pragma mark - undo mechanism -(void)undo{ if ([_currentTouch count]) { [_redoStore addObject:[NSArray arrayWithArray:_currentTouch]]; [_currentTouch removeAllObjects]; [self setNeedsDisplay]; }else if ([_touches count]){ [_redoStore addObject:[_touches lastObject]]; [_touches removeLastObject]; [self setNeedsDisplay]; }else{ //nothing left to undo } } -(void)redo{ if ([_redoStore count]) { [_touches addObject:[_redoStore lastObject]]; [_redoStore removeLastObject]; [self setNeedsDisplay]; } } @end