Undo / Redo for drawing in iOS

I am working on a graphical application, I want to make Undo / Redo, for this I save CGPath for touches ending before NSMutableArray, but I don’t understand how I should display CGPaths when I click Cancel

EDIT1:

How I use BezierPaths. So, I first decided to go with a simple approach, just pat this way, without CGPath.

EDIT2: Since my Undo happens in segments (i, e is piecemeal and not the whole path is deleted), I decided to create an array of arrays, so I made the appropriate changes, and now I will draw CGlayer, with the adoption of CGPath

So here "parentUndoArray" is an array of arrays.

So i did it

I have a DrawingPath class that will do the drawing

//DrawingPath.h @interface DrawingPath : NSObject @property (strong, nonatomic) NSString *pathWidth; @property (strong,nonatomic) UIColor *pathColor; @property (strong,nonatomic) UIBezierPath *path; - (void)draw; @end //DrawingPath.m #import "DrawingPath.h" @implementation DrawingPath @synthesize pathWidth = _pathWidth; @synthesize pathColor = _pathColor; @synthesize path = _path; - (id)init { if (!(self = [super init] )) return nil; _path = [[UIBezierPath alloc] init]; _path.lineCapStyle=kCGLineCapRound; _path.lineJoinStyle=kCGLineJoinRound; [_path setLineWidth:2.0f]; return self; } - (void)draw { [self.pathColor setStroke]; [self.path stroke]; } 

So now in my DrawingView I do it like this

 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { ctr = 0; bufIdx = 0; UITouch *touch = [touches anyObject]; pts[0] = [touch locationInView:self]; isFirstTouchPoint = YES; [m_undoArray removeAllObjects];//On every touches began clear undoArray } -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint p = [touch locationInView:self]; ctr++; pts[ctr] = p; if (ctr == 4) { pts[3] = midPoint(pts[2], pts[4]); for ( int i = 0; i < 4; i++) { pointsBuffer[bufIdx + i] = pts[i]; } bufIdx += 4; dispatch_async(drawingQueue, ^{ self.currentPath = [[DrawingPath alloc] init]; [self.currentPath setPathColor:self.lineColor]; if (bufIdx == 0) return; LineSegment ls[4]; for ( int i = 0; i < bufIdx; i += 4) { if (isFirstTouchPoint) // ................. (3) { ls[0] = (LineSegment){pointsBuffer[0], pointsBuffer[0]}; [self.currentPath.path moveToPoint:ls[0].firstPoint]; // [offsetPath addLineToPoint:ls[0].firstPoint]; isFirstTouchPoint = NO; } else { ls[0] = lastSegmentOfPrev; } float frac1 = self.lineWidth/clamp(len_sq(pointsBuffer[i], pointsBuffer[i+1]), LOWER, UPPER); // ................. (4) float frac2 = self.lineWidth/clamp(len_sq(pointsBuffer[i+1], pointsBuffer[i+2]), LOWER, UPPER); float frac3 = self.lineWidth/clamp(len_sq(pointsBuffer[i+2], pointsBuffer[i+3]), LOWER, UPPER); ls[1] = [self lineSegmentPerpendicularTo:(LineSegment){pointsBuffer[i], pointsBuffer[i+1]} ofRelativeLength:frac1]; // ................. (5) ls[2] = [self lineSegmentPerpendicularTo:(LineSegment){pointsBuffer[i+1], pointsBuffer[i+2]} ofRelativeLength:frac2]; ls[3] = [self lineSegmentPerpendicularTo:(LineSegment){pointsBuffer[i+2], pointsBuffer[i+3]} ofRelativeLength:frac3]; [self.currentPath.path moveToPoint:ls[0].firstPoint]; // ................. (6) [self.currentPath.path addCurveToPoint:ls[3].firstPoint controlPoint1:ls[1].firstPoint controlPoint2:ls[2].firstPoint]; [self.currentPath.path addLineToPoint:ls[3].secondPoint]; [self.currentPath.path addCurveToPoint:ls[0].secondPoint controlPoint1:ls[2].secondPoint controlPoint2:ls[1].secondPoint]; [self.currentPath.path closePath]; lastSegmentOfPrev = ls[3]; // ................. (7) } [m_undoArray addObject:self.currentPath]; EDIT:2 CGPathRef cgPath = self.currentPath.path.CGPath; mutablePath = CGPathCreateMutableCopy(cgPath); dispatch_async(dispatch_get_main_queue(), ^{ bufIdx = 0; [self setNeedsDisplay]; }); }); pts[0] = pts[3]; pts[1] = pts[4]; ctr = 1; } } } } -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { [parentUndoArray addObject:m_undoArray]; } 

My drawRect method below

EDIT: now my DrawRect has two cases

  - (void)drawRect:(CGRect)rect { switch (m_drawStep) { case DRAW: { CGContextRef context = UIGraphicsGetCurrentContext();//Get a reference to current context(The context to draw) CGContextRef layerContext = CGLayerGetContext(self.currentDrawingLayer); CGContextBeginPath(layerContext); CGContextAddPath(layerContext, mutablePath); CGContextSetStrokeColorWithColor(layerContext, self.lineColor.CGColor); CGContextSetFillColorWithColor(layerContext, self.lineColor.CGColor); CGContextSetBlendMode(layerContext,kCGBlendModeNormal); CGContextDrawPath(layerContext, kCGPathFillStroke); // CGPathRelease(mutablePath); CGContextDrawLayerInRect(context,rectSize, self.newDrawingLayer); CGContextDrawLayerInRect(context, self.bounds, self.permanentDrawingLayer); CGContextDrawLayerInRect(context, self.bounds, self.currentDrawingLayer ); } break; case UNDO: { for(int i = 0; i<[m_parentUndoArray count];i++) { NSMutableArray *undoArray = [m_parentUndoArray objectAtIndex:i]; for(int i =0; i<[undoArray count];i++) { DrawingPath *drawPath = [undoArray objectAtIndex:i]; [drawPath draw]; } } } break; [super drawRect:rect]; } 

EDIT2: Now the problem I'm currently facing is that even if I draw small paths or large paths, the data in the array array is the same. But infact, the small path should contain a smaller drawingPath object and the large path should contain more drawPath objects in undoArray, which is finally added to the array array called "ParentUndoArray

Here are the screenshots

1.first screen Shot of a drawn line on a stretch without raising a finger

enter image description here

2.After a cancel operation, only a segment of this row is deleted once

enter image description here

+2
ios uikit core-graphics cgpath
Jan 29 '14 at 17:43
source share
4 answers

I found a solution for this, we need to create an array of the DrawingPaths array:

 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { // Do the above code, then [m_undoArray addObject:self.currentPath]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { [m_parentUndoArray addObject:[NSArray arrayWithArray:m_undoArray]]; } 

and then put the path in DrawRect .

+2
Feb 02 '14 at 4:20
source share
β€” -

If your drawRect method can draw all the CGPaths in the array, all you have to do is run the drawing again in your Undo method by calling setNeedsDisplay after removing the last added CGPath

+1
Jan 29 '14 at 18:45
source share

I think you are creating more path objects than you plan. I suggest moving to where you drag bezierPath to touch, moved and replacing

 self.currentPath = [[DrawingPath alloc] init]; 

FROM

 if(!self.currentPath){ self.currentPath = [[DrawingPath alloc] init]; } 
+1
Jan 31 '14 at 12:34
source share

Well, you have to do it like its easy

 - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // Initialization code self.backgroundColor = [UIColor clearColor]; myPath = [[UIBezierPath alloc] init]; myPath.lineCapStyle = kCGLineCapRound; myPath.miterLimit = 0; bSize=5; myPath.lineWidth = bSize; brushPattern = [UIColor whiteColor]; // Arrays for saving undo-redo steps in arrays pathArray = [[NSMutableArray alloc] init]; bufferArray = [[NSMutableArray alloc] init]; } return self; } // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. - (void)drawRect:(CGRect)rect { [brushPattern setStroke]; for (id path in pathArray){ if ([path isKindOfClass:[UIBezierPath class]]) { UIBezierPath *_path=(UIBezierPath *)path; [_path strokeWithBlendMode:kCGBlendModeNormal alpha:1.0]; } } } #pragma mark - Touch Methods -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *mytouch = [[touches allObjects] objectAtIndex:0]; myPath = [[UIBezierPath alloc] init]; myPath.lineWidth = bSize; [myPath moveToPoint:[mytouch locationInView:self]]; [pathArray addObject:myPath]; } -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { [myPath addLineToPoint:[[touches anyObject] locationInView:self]]; [self setNeedsDisplay]; } #pragma mark - Undo Method -(void)undoButtonClicked { if([pathArray count]>0) { if ([[pathArray lastObject] isKindOfClass:[SPUserResizableView class]]) { [[pathArray lastObject] removeFromSuperview]; } UIBezierPath *_path = [pathArray lastObject]; [bufferArray addObject:_path]; [pathArray removeLastObject]; [self setNeedsDisplay]; } } -(void)setBrushSize: (CGFloat)brushSize { bSize=brushSize; } -(void)redoButtonClicked { if([bufferArray count]>0){ UIBezierPath *_path = [bufferArray lastObject]; [pathArray addObject:_path]; [bufferArray removeLastObject]; [self setNeedsDisplay]; } } -(void)undoAllButtonClicked { [pathArray removeAllObjects]; [self setNeedsDisplay]; } 

Hope this helps.

0
Feb 21 '14 at 14:17
source share



All Articles