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

时间:2014-01-09 18:03:16

标签: ios uikit core-graphics cgcontext cglayer

我正在使用绘图应用程序,我使用CGlayers进行绘图。在触摸结束时,我从图层中取出图像并将其存储在数组中,我用它来撤消操作。

我的触摸结束了功能

-(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];
}

我的绘图视图根据用户需求动态扩展,因此假设用户可以绘制say,一条drawView大小为200 * 200的行,然后将其展开为200 * 300并再绘制一行,然后将其展开为200 * 300并绘制还有一行。

以下是该应用的图片

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

每当我增加/减少画布大小时。我写了这段代码

在drawingView的增加和减少时,我正在写这个函数

 (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. 这里发生的事情是,我的撤消数组中的图像不尊重rects,而是在新图层的任意位置绘制。

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

2 个答案:

答案 0 :(得分:4)

首先,由于您正在使用图层,因此我建议您放弃drawRect:并使用CALayer转换。

其次,在我看来,实现undo-redo操作的最佳方法始终是基于命令的。作为一个非常简单的示例,您可以为每个命令创建单独的方法:

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

答案 1 :(得分:2)

每个触摸事件的图像对象是一个坏主意恕我直言,你正在撕裂ram。为什么不保留一系列触摸点并动态绘制?很容易从该数组中删除最后几个元素以进行廉价的撤消操作

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

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

享受:)

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