Saving CGContextRef

I have a drawing application in which I would like to create a cancel method. The drawing is executed inside the TouchesMoved method:

I am trying to create a CGContextRef and push it onto the stack OR save it in a context property that can be restored later, but I'm out of luck. Any advice would be appreciated. Here is what I have ...

UIImageView *drawingSurface; CGContextRef undoContext; - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UIGraphicsBeginImageContext(self.view.frame.size); CGContextRef context = UIGraphicsGetCurrentContext(); [drawingSurface.image drawInRect:CGRectMake(0, 0, drawingSurface.image.size.width, drawingSurface.image.size.height)]; UIGraphicsPushContext(context); // also tried but cant figure how to restore it undoContext = context; UIGraphicsEndImageContext(); } 

Then I have a method initiated by my cancel button ...

 - (IBAction)restoreUndoImage { UIGraphicsBeginImageContext(self.view.frame.size); UIGraphicsPopContext(); drawingSurface.image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); } 

When I run this, I find that my drawing is set to Surface nil because it just erases everything in the image.

My guess: I can't use pop and push this way. But I can’t understand how easy it is to save the context and then bring it back to the drawing. Hmmm. Any help would be ... well ... helpful. Thanks in advance -

And, just for reference, here is what I do to draw on the screen, which works great. This is inside my TouchesMoved:

  UIGraphicsBeginImageContext(self.view.frame.size); CGContextRef context = UIGraphicsGetCurrentContext(); [drawingSurface.image drawInRect:CGRectMake(0, 0, drawingSurface.image.size.width, drawingSurface.image.size.height)]; CGContextSetLineCap(context, kCGLineCapRound); //kCGLineCapSquare, kCGLineCapButt, kCGLineCapRound CGContextSetLineWidth(context, self.brush.size); // for size CGContextSetStrokeColorWithColor (context,[currentColor CGColor]); CGContextBeginPath(context); CGContextMoveToPoint(context, lastPoint.x, lastPoint.y); CGContextAddLineToPoint(context, currentPoint.x, currentPoint.y); CGContextStrokePath(context); drawingSurface.image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); 
+6
objective-c uiview quartz-2d cgcontext
source share
1 answer

I think you are approaching the problem in the wrong way and confusing contexts.

In the Instant Mode API, you save the "state" of objects using push / pop, not a graphical representation. A condition consists of things such as line width, color, and position. The graphical representation is the result of a drawing operation (bitmap) and, as a rule, what you do not want to save.

Instead, try to save the "information" that you use to create the drawing.

My initial suggestion was to separate your figure creation and painting. In OSX, you can use NSBezierPath, but for iOS we must use an array of points.

For example, this protocol:

 // ViewController.h @protocol DrawSourceProtocol <NSObject> - (NSArray*)pathsToDraw; @end @interface ViewController : UIViewController<DrawSourceProtocol> @end 

You can implement the following functions:

 // ViewController.m @interface ViewController () { NSMutableArray *currentPath; NSMutableArray *allPaths; MyView *view_; } @end ... - (void)viewDidLoad { [super viewDidLoad]; currentPath = [[NSMutableArray alloc] init]; allPaths = [[NSMutableArray alloc] init]; view_ = (MyView*)self.view; view_.delegate = self; } - (NSArray*)pathsToDraw { // Return the currently draw path too if (currentPath && currentPath.count) { NSMutableArray *allPathsPlusCurrent = [[NSMutableArray alloc] initWithArray:allPaths]; [allPathsPlusCurrent addObject:currentPath]; return allPathsPlusCurrent; } return allPaths; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { currentPath = [[NSMutableArray alloc] init]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { // When a touch ends, save the current path [allPaths addObject:currentPath]; currentPath = [[NSMutableArray alloc] init]; [view_ setNeedsDisplay]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint currentPoint = [touch locationInView:self.view]; // We store the point with the help of NSValue [currentPath addObject:[NSValue valueWithCGPoint:currentPoint]]; // Update the view [view_ setNeedsDisplay]; } 

Now subclass your view (I call my MyView here) and implement something like this:

 // MyView.h #import "ViewController.h" @protocol DrawSourceProtocol; @interface MyView : UIView { __weak id<DrawSourceProtocol> delegate_; } @property (weak) id<DrawSourceProtocol> delegate; @end // MyView.m @synthesize delegate = delegate_; ... - (void)drawRect:(CGRect)rect { NSLog(@"Drawing!"); // Setup a context CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor); CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0); CGContextSetLineWidth(context, 2.0); // Get the paths NSArray *paths = [delegate_ pathsToDraw]; for (NSArray *aPath in paths) { BOOL firstPoint = TRUE; for (NSValue *pointValue in aPath) { CGPoint point = [pointValue CGPointValue]; // Always move to the first point if (firstPoint) { CGContextMoveToPoint(context, point.x, point.y); firstPoint = FALSE; continue; } // Draw a point CGContextAddLineToPoint(context, point.x, point.y); } } // Stroke! CGContextStrokePath(context); } 

The only lane here is that setNeedsDisplay is not very efficient. Better to use setNeedsDisplayInRect: see my last post regarding an effective way to define a "drawn" rectangle .

Regarding cancellation? Your undo operation simply pops the last object from the allPaths array. In this exercise I will leave you :)

Hope this helps!

+1
source share

All Articles