1

I am working with unod redo operations on CgLayer, I have tried some code, but not able to get it working, dont know , where I am getting wrong, below is my code, which i have written

this is my drawRect function

- (void)drawRect:(CGRect)rect
{    
    m_backgroundImage = [UIImage imageNamed:@"bridge.jpg"];

    CGPoint drawingTargetPoint = CGPointMake(0,0);
    [m_backgroundImage drawAtPoint:drawingTargetPoint];


    switch(drawStep)
    {
          case DRAW:
          {
              CGContextRef context = UIGraphicsGetCurrentContext();

              if(myLayerRef == nil)
              {

                  myLayerRef = CGLayerCreateWithContext(context, self.bounds.size, NULL);
              }   

              CGContextDrawLayerAtPoint(context, CGPointZero, myLayerRef); 
              break;
          }           

         case UNDO:
         {            
              [curImage drawInRect:self.bounds];              
              break;
         }

        default:
            break;
    }      
}

On touches ended , I am converting the layer into NSValue and storing as keyValue pair into NSDictionary and then adding the dictionary object to array.

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{        
    NSValue *layerCopy = [NSValue valueWithPointer:myLayerRef];


    NSDictionary *lineInfo = [NSDictionary dictionaryWithObjectsAndKeys:layerCopy, @"IMAGE",
                              nil];

    [m_pathArray addObject:lineInfo];    
    NSLog(@"%i",[m_pathArray count]);

}

below is my Undo functionality

- (void)undoButtonClicked
{   
    if([m_pathArray count]>0)
    {
        NSMutableArray *_line=[m_pathArray lastObject];
        [m_bufferArray addObject:[_line copy]];
        [m_pathArray removeLastObject];
        drawStep = UNDO;
        [self redrawLine];
    } 
}

//Redraw functions

- (void)redrawLine
{
    NSDictionary *lineInfo = [m_pathArray lastObject];

    NSValue *val = [lineInfo valueForKey:@"IMAGE"];

    CGLayerRef  layerToShow = (CGLayerRef) [val pointerValue];

    CGContextRef context1 = CGLayerGetContext(layerToShow);
    CGContextDrawLayerAtPoint(context1, CGPointMake(00, 00),layerToShow);
    [self setNeedsDisplayInRect:self.bounds];
}

I think its here where I am goin wrong. So friends please help me out.

From the comments below, I have added the function, where its draws into Cglayer(this function I am calling into touchesMovedEvent.

- (void) drawingOperations
{    
    CGContextRef context1 = CGLayerGetContext(myLayerRef);    

    CGPoint mid1 = midPoint(m_previousPoint1, m_previousPoint2); 
    CGPoint mid2 = midPoint(m_currentPoint, m_previousPoint1);     

    CGContextMoveToPoint(context1, mid1.x, mid1.y);
    CGContextAddQuadCurveToPoint(context1, m_previousPoint1.x, m_previousPoint1.y, mid2.x, mid2.y); 
    CGContextSetLineCap(context1, kCGLineCapRound);
    CGContextSetLineWidth(context1, self.lineWidth);
    CGContextSetStrokeColorWithColor(context1, self.lineColor.CGColor);           
    CGContextSetAllowsAntialiasing(context1, YES);
    CGContextSetInterpolationQuality(context1, kCGInterpolationHigh); 
    CGContextSetAlpha(context1, self.lineAlpha);
    CGContextStrokePath(context1);    
}

Regards Ranjit

Ranjit
  • 4,576
  • 11
  • 62
  • 121
  • 1
    You're not drawing anything into the layer's context. Your code in `redrawLine` is the only code you showed that even gets the layer's context, but all it does with it is attempt to draw the layer into itself. So, aside from the lack of any drawing, what do you mean by this code not working? – Peter Hosey Jul 10 '12 at 18:42
  • Hello Peter,thanks for your reply. Not drawing into layer,s context, you mean into redrawLine function or Case: DRAW?. I think you are relating to redrawLine function because in Case:DRAW, I am drawing into Layer's context. Second thing, whether I should store layers for undo redo functionality, or something else, whether my "touchesEnded" function correct? because I tried to debug it with NSLog, and everytime , the NSValue for CgLayer is same. So I confused their. – Ranjit Jul 11 '12 at 06:36
  • 1
    No, `drawRect:` does not draw into the layer's context. The `DRAW` case in `drawRect:` draws the layer into UIKit's current context—i.e., to the screen. The code in `redrawLine` does draw into the layer's context, but it also is drawing the layer into that context—as I said, drawing the layer into itself. – Peter Hosey Jul 11 '12 at 08:01
  • thanks for your reply, then please tell me how it should be done? – Ranjit Jul 11 '12 at 09:19
  • Hey sorry, the drawing into CgLayer, I have written it into different function, and that I am calling in touchesMovedEvent,I have updated the above code, please check it. – Ranjit Jul 11 '12 at 09:27
  • Hello @PeterHosey hope its correct now.any suggestions? – Ranjit Jul 11 '12 at 10:27
  • 1
    Well, now you have a method that draws into the layer, but at what point do you call it? None of the code you've shown does. – Peter Hosey Jul 11 '12 at 10:46
  • As I said,in the above lines, I am calling it in touchesMovedEvent.Do you want to see that function? – Ranjit Jul 11 '12 at 10:57
  • Hello @PeterHosey, please tell me where I am going wrong? – Ranjit Jul 11 '12 at 11:20
  • Hi @PeterHosey: Did you checked my code? – Ranjit Jul 12 '12 at 07:05
  • Hello friends, I am badly stuck into this, any info regarding this would be helpful, so please comment – Ranjit Jul 13 '12 at 08:48
  • Please check my answer, i have checked you code needs enhancement so i have posted some easy way. – Mina Nabil Jul 18 '12 at 12:36
  • Hello @PeterHosey, please have look at this http://stackoverflow.com/questions/21027788/doing-undo-and-redo-with-cglayer-drawing – Ranjit Jan 13 '14 at 07:20

2 Answers2

1

The best way to implement Undo and Redo is to implement NSUndoManager as a brief description of it you don't have to save each state of Object that you want to undo or redo the NSUndoManager itself make this for you ...

Steps for reaching this is:

1- Initialize NSUndoManager.

2- Register Object state in the NSUndoManager object with specified function call will discuss this for you case later.

3-use undo or redo or clear actions function in NSUndoManager Object.

Ex from my working solution

-in .h file

@property (nonatomic,retain) NSUndoManager *undoManager;
  • in .m file

    @synthesize undoManager;

  • in "viewDidLoad" method -- initialize your NSUndoManager

    undoManager = [[NSUndoManager alloc] init];

Suppose that you have a function in your class that zoom in/out using pinch so in your "viewDidLoad" you will have

UIPinchGestureRecognizer *pinchGesture =
    [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinch:)];
    pinchGesture.delegate = (id)self;
    [self.view addGestureRecognizer:pinchGesture];

So pinch will zoom in/out such Note that "MyImageView" is the image that we want to zoom in/out

- (void)pinch:(UIPinchGestureRecognizer*)recognizer{

    if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateChanged) {
        NSLog(@"gesture.scale = %f", recognizer.scale);

        CGFloat currentScale = self.MyImageView.frame.size.width / self.MyImageView.bounds.size.width;
        CGFloat newScale = currentScale * recognizer.scale;
        //Here is the line that register image to NSUndoManager before making and adjustments to the image "save current image before changing the transformation"
        //Add image function is function that fired when you Make undo action using NSUndoManager "and so we maintain only image transformation that changed when you zoom in/out"
         [[undoManager prepareWithInvocationTarget:self] AddImage:self.MyImageView.transform];

        if (newScale < 0.5) {
            newScale = 0.5;
        }
        if (newScale > 5) {
            newScale = 5;
        }

        CGAffineTransform transform = CGAffineTransformMakeScale(newScale, newScale);
        self.MyImageView.transform = transform;
        recognizer.scale = 1;
    }
}

-AddImage Function will save current image transformation state in NSUndoManager.

-(void)AddImage:(CGAffineTransform)sender{
    CGAffineTransform transform = sender;
    self.MyImageView.transform = transform;
}

So if you have button that make Undo action

-(IBAction)OptionsBtn:(id)sender{
  if ([undoManager canUndo]) {
      [undoManager undo];
  }
}

So if you want to cancel all action you have both ways

while ([undoManager canUndo]) {
   [undoManager undo];
}

OR

[undoManager removeAllActions];
Mina Nabil
  • 676
  • 6
  • 19
  • Hey Mina Nabil, thanks for your reply, I came to know recently that from wwdc 2012 sessions, that CgLayer will be depricated and instead CALayer should be used. But I have decided to do it the normal way and not using CGLayer or CALayer. But I would like to implement this NSUndoManager, but before that please have a look at this link, where I have the code, how I have implemented undo and redo http://stackoverflow.com/questions/11502320/eraser-not-working-in-ios-drawing – Ranjit Jul 18 '12 at 13:09
  • Yes ok np for me , but did you understand first the concept of NSUndoManger and how it's working if so you can make it your self i have np to implement it for you but the purpose here is to understand and analyze.. anyway i will help you in you CALayer Code. – Mina Nabil Jul 18 '12 at 13:33
  • No I got it partially. did you check my link? What you have to say? why my eraser is not working? – Ranjit Jul 18 '12 at 13:56
0
- (void)redrawLine
{
    NSDictionary *lineInfo = [m_pathArray lastObject];
    NSValue *val = [lineInfo valueForKey:@"IMAGE"];
    CGContextRef context1 = (CGContextRef) [val pointerValue];
    CGContextDrawLayerAtPoint(context1, CGPointMake(00, 00),layerToShow);
    [self setNeedsDisplayInRect:self.bounds];
}

just update this method with ur code

Jayesh Thanki
  • 2,037
  • 2
  • 23
  • 32
Rahul Juyal
  • 2,124
  • 1
  • 16
  • 33