Doing Undo and Redo with Cglayer Drawing

随声附和 提交于 2019-11-27 16:20:21

First of all, since you are working with layers, I suggest give up on drawRect: and just work with CALayer transforms.

Second, in my opinion, the best way to implement undo-redo operations will always be command-based. As a very simple example, you can make separate methods for each command:

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

And then each time the user makes an action, you add to an NSMutableArray the action id and the parameters:

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

Conversely, if the user invokes undo, remove the last object in that array.

Then when you need to reload the display, just reevaluate all the commands in the array.

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

An image object for each touch event is a bad idea IMHO, you're tearing through ram. Why not keep an array of touch points and draw dynamically? Easy enough to remove the last few elements from that array for a cheap undo operation

////14 Jan 2014// //edit to include example//

OK here is a quick drawing view example. there are three mutableArrays, _touches, which is for all previous drawings, _currentTouch, which is the current drawing and only contains data during touch events, (between touches began and touches ended).. and a redo array that data which is removed by undo is copied to rather than just deleting it (which you can certainly do)

enjoy :)

//
//  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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!