Doing Undo and Redo with Cglayer Drawing

后端 未结 2 1980
粉色の甜心
粉色の甜心 2020-12-04 02:56

I am working with a drawing app, I am using CGlayers for drawing. On touches ended, I get image out of the layer and store it in a Array, which I use to undo operation.

相关标签:
2条回答
  • 2020-12-04 03:03

    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
    }
    
    0 讨论(0)
  • 2020-12-04 03:20

    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
    
    0 讨论(0)
提交回复
热议问题