Cannot enable undo / redo with smooth free drawing

Fighting the problem since many days. I hope I get an answer here. I used this link to smooth my drawing in my free hand. In this code, I managed to set the width and color of the line, but I find it difficult to do this when I try to include the undo / redo function in it using this , which works great when undoing the redo, but its free manual training is not smooth.

After some research and coding, I should know that this is a drawing cutting which, I think, prevents undo / redo.

The first link contains the file "CachedLIView.h / m", when I used this and try to include undo / redo in it, I found that in the following method:

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event // (2) { UITouch *touch = [touches anyObject]; CGPoint p = [touch locationInView:self]; [path addLineToPoint:p]; [self drawBitmap]; // (3) [self setNeedsDisplay]; [path removeAllPoints]; //(4) } 

this method calls the drawBitMap method: a method that actually generates a temporary image each time the user lifts his finger and at the same time removes points from the "path".

 - (void)drawBitmap // (3) { UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, 0.0); [[UIColor blackColor] setStroke]; if (!incrementalImage) // first draw; paint background white by ... { UIBezierPath *rectpath = [UIBezierPath bezierPathWithRect:self.bounds]; // enclosing bitmap by a rectangle defined by another UIBezierPath object [[UIColor greenColor] setFill]; [rectpath fill]; // filling it with white } [incrementalImage drawAtPoint:CGPointZero]; //[path stroke]; for (UIBezierPath *_path in pathArray) [_path strokeWithBlendMode:kCGBlendModeNormal alpha:1.0]; incrementalImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); } 

I save each path in an array so that I can undo / redo. (took this idea from the second link).

The following is the full code for this file, which I modified to include undo / redo:

  #import "CachedLIView.h" @implementation CachedLIView { UIBezierPath *path; UIImage *incrementalImage; // (1) } - (id)initWithFrame:(CGRect)frame // (1) { if (self = [super initWithFrame:frame]) { [self setMultipleTouchEnabled:NO]; // (2) // [self setBackgroundColor:[UIColor whiteColor]]; // path = [[UIBezierPath alloc] init]; // [path setLineWidth:3]; pathArray=[[NSMutableArray alloc]init]; bufferArray=[[NSMutableArray alloc]init]; [self drawBitmap]; } return self; } - (void)drawRect:(CGRect)rect { NSLog(@"in drawrect pathArray[count]: %d", pathArray.count); [incrementalImage drawInRect:rect]; // (3) //[[UIColor blackColor] setStroke]; //[path stroke]; for (UIBezierPath *_path in pathArray) [_path strokeWithBlendMode:kCGBlendModeNormal alpha:1.0]; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { path = [[UIBezierPath alloc] init]; path.lineWidth = 3; UITouch *touch = [touches anyObject]; CGPoint p = [touch locationInView:self]; [path moveToPoint:p]; [pathArray addObject:path]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint p = [touch locationInView:self]; [path addLineToPoint:p]; [self setNeedsDisplay]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event // (2) { UITouch *touch = [touches anyObject]; CGPoint p = [touch locationInView:self]; [path addLineToPoint:p]; [self drawBitmap]; // (3) [self setNeedsDisplay]; [path removeAllPoints]; //(4) } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { [self touchesEnded:touches withEvent:event]; } - (void)drawBitmap // (3) { UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, 0.0); [[UIColor blackColor] setStroke]; if (!incrementalImage) // first draw; paint background white by ... { UIBezierPath *rectpath = [UIBezierPath bezierPathWithRect:self.bounds]; // enclosing bitmap by a rectangle defined by another UIBezierPath object [[UIColor greenColor] setFill]; [rectpath fill]; // filling it with white } [incrementalImage drawAtPoint:CGPointZero]; //[path stroke]; for (UIBezierPath *_path in pathArray) [_path strokeWithBlendMode:kCGBlendModeNormal alpha:1.0]; incrementalImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); } #pragma mark - undo/redo -(void)undoButtonClicked { if([pathArray count]>0){ UIBezierPath *_path=[pathArray lastObject]; [bufferArray addObject:_path]; [pathArray removeLastObject]; [self drawBitmap]; [self setNeedsDisplay]; } } -(void)redoButtonClicked { if([bufferArray count]>0){ UIBezierPath *_path=[bufferArray lastObject]; [pathArray addObject:_path]; [bufferArray removeLastObject]; [self drawBitmap]; [self setNeedsDisplay]; } } @end 

.h file:

 #import <UIKit/UIKit.h> @interface CachedLIView : UIView { NSMutableArray *pathArray; NSMutableArray *bufferArray; UIBezierPath *myPath; } -(void)undoButtonClicked; -(void)redoButtonClicked; @end 

Please help me. What am I doing wrong. The pathArray counter is working correctly. but cannot show the cancel / redo effect on the screen.

+4
source share
2 answers

Finally, I get it and can have smooth undo / redo functions. It was a drawBitMap method that caused problems.

I removed (commented) the call to this method [self drawBitmap] everywhere.

I called this method in touchs.Ended and in undo / redo methods. I did not need to use this method, since it performs caching (to speed up drawing) whenever the user lifts his fingers from the screen, a new image is created and placed on the screen, and the old one is deleted, and the user has the illusion that he is constantly drawing . But this caching is only necessary in an extreme situation when you really want to improve performance (if you go out of memory and your drawing starts to get ugly).

So, I decided to keep this caching mechanism at a later stage and delete it. Although I was not able to find out why my undo / redo function does not work with it, but I think that when I had a new image caching and when it was placed on the screen (when the user lifted his fingers), then this created problems, since I need to save the last cached image (for undo / redo).

I will use caching at a later stage and try to optimize my code as well.

Also be sure to remove [myPath removeAllPoints]; this line is from touchEnded: method, your other wise drawing will disappear as soon as you raise your fingers.

Hope this helps someone. Here is the modified touchesEnded method:

 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event // (2) { UITouch *touch = [touches anyObject]; CGPoint p = [touch locationInView:self]; [path addLineToPoint:p]; //[self drawBitmap]; // CAUSES PROBLEM IF UNCOMMENTED AND UNDO/REDO WILL STOP WORKING.. [self setNeedsDisplay]; //[myPath removeAllPoints];// LINE GETS DRAWN BUT VANISHED WHEN TOUCH LIFTED OFF FROM SCREEN.. ctr = 0; } 

Later I added this function to SmoothedBIView and its work is very beautiful.

+1
source

Hey, I can’t get all your code, but I can offer you how we can implement such things,

To repeat and undo in a graphic image, you can capture image render while touching or touch the end and manage stack of that renders . Then, as per your requirement (redo / undo), use this rendering from your cache. So the steps are like,

1) Capturing rendering and saving when you touch your board. and manage this sequence in your cache.

  • So, for every touch you need to record an image and save it sequentially.

2) When you click cancel at this time, get the last shot and replace it with your board.

3) To repeat, you can check if there is a higher render than your current replaced render. means that you can enable and disable this button with the latest updated render.

4) Remember to clear your cache after the image is fully saved, so you can control the stack for the next image.


  • let me know if you do not get this idea.
+2
source

All Articles