iPhone - trying to understand UIBezier in CGContext - iphone

When I have a UIBezier and I stoke it on a view using drawRect I see the bezier is a a kind of volatile, I mean, it is really not drawn anywhere. Instead it is like it was written in a kind of buffer that corresponds to what I see on that view. I mean, if I invoke drawRect again using [self setNeedsDisplay] the bezier is gone and I can write other stuff.
On the other hand, if I draw a like on a CGContext using
CGContextStrokePath(ctx);
I am really writing the line to that context and there's no way to erase it and if I call the same method again, it will write over the context that now has already one line drawn.
But what happens when I use a UIBezier command to write on a CGContext?
I mean, if I do something like
UIGraphicsPushContext(ctx);
CGContextSaveGState(ctx);
CGContextTranslateCTM(ctx, 0, height);
CGContextScaleCTM(ctx, 1.0, -1.0);
[myBezier stroke];
CGContextRestoreGState(ctx);
UIGraphicsPopContext();
is this line permanently written to ctx or is it volatile as before? The docs contain no information about it and are vague as always.
thanks

Whenever your view's drawRect: gets called, UIKit has set up a graphics context for you to draw into. That's why it appears that the bezier paths aren't permanent; every time the view needs to be displayed there's a fresh new context, and your paths get drawn into that. Each time through drawRect:, therefore, you can decide not to draw a particular path and it won't be displayed.
There's no way to remove a path once it's been added to the context, but the next time the view is refreshed, there's a new context with almost nothing in it. If you create your own graphics context, whatever you put into it is "permanent" -- it will be there for the lifetime of the context.
The context basically consists of drawing instructions that will produce a "picture". This is vague because the destination for a context -- where the picture will be drawn -- can be different things: an image file, a section of the device screen, possibly even a piece of paper. The context, once it's full of drawing instructions, then gets rendered to its destination.
In the case of your view drawing, UIKit sets up that context for you and calls your drawRect:. There's a certain amount of indirection here -- you're never really "drawing into a view", always into a context. Your view essentially reserves and represents a section of the screen. UIKit asks you what you would like to put into that section, and gives you the graphics context so that you can convey that information. Then it takes the context, which is full of instructions, turns it into pixel data, and paints that in the area represented by your view.

UIBezierPath is a class that encompasses a series of lines/curves and instructions on how to draw them. When you draw a bezier path, it essentially does the same thing as any other drawing function in Quartz. The content of the context will persist for the lifetime of the context (for a bitmap context, that's the lifetime of the bitmap in memory; for a view context, that's "until the view needs to be redrawn.")

Related

Drawing a straight line between two points by dragging on IOS

I have two square images in my UIVIew. Once I drag my finger from one image to another image I want to draw a straight line between them.
I have handled touchesMoved method to check when my touchLocation reaches the frame of either of the images. So I have handled the logic part of when to start drawing and between which two points.
I just cant figure out how to do that using (void)drawRect:(CGRect)rect. For one thing, I addded an NSlog in my drawRect and I wrote code to draw a line between two lines, even that's not happening.
I checked this question too, but I want to continue to drag and draw lines between multiple points.
- (void)drawRect:(CGRect)rect
{
NSLog(#"Draw Rect Entered");
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetStrokeColorWithColor(context, [[UIColor blackColor]CGColor]);
CGContextSetLineWidth(context, 1.0);
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, 20, 20);
CGContextStrokePath(context);
}
In order for the view to update you need to call -setNeedsDisplay or -setNeedsDisplayInRect: on the view that needs to be redrawn. You should do this in the touch handler once you have determined you need to draw the line. This will trigger the -drawRect: method on the view- you don't call -drawRect directly. For more information, read the UIView docs. I don't know whether you want your line to exist permanently once created- if there's a possibility it might be deleted, I'd add an if statement to -drawRect: controlled by a boolean determining whether to draw the line or not. Then you can easily switch it off if necessary.
Based on your comments below, I'd create an ivar array in which to store the points for the currently traced line and then loop over them in -drawRect:, using the Core graphics functions to draw the line a segment at a time. That way the new segment and all previous segments will be drawn with each redraw. Since CGPoint isn't an obj-c object, you'll either have to create a wrapper class or use obj-C++ and std::vector. You could also use a fixed size C-Array, (you mention you have precisely 10 points)- in that case you'd have to preset the unused coordinates to some arbitrary value defined as invalid (e.g. -500,-500) and add logic to not draw line segments if those values are encountered.
Minor point- I wouldn't hardcode the line coordinates - instead you could derive them from the images' frame property. That way your code will be more readable and won't break if you change the images or resize them.
There are two issues I have run into that could cause this problem:
1) Make sure you are calling setNeedsDisplay on the main thread
2) If you are working with multiple views make sure you are calling setNeedsDisplay against the correct one.
It is easy to work with one view and forget to call setNeedsDisplay against the parent view.

How to create a CGLayer from a UIView for off-screen drawing

I have read what I believe to be the relevant parts of the Quartz 2D Programming Guide, but cannot find an answer to the following (they don't seem to talk a lot about iOS in the document):
My application displays a drawing in a UIView. Every now and then I have to update the drawing in some way, e.g. change the fill colour of one of the shapes (I keep CGPathRefs to the important shapes to be able to redraw them with a different fill colour later). As described in the Section "Drawing With a CGLayer" on page 169 of the aforementioned document, I was thinking of drawing the entire drawing into a CGContext that I would obtain from a CGLayer, like so:
CGContextRef offscreenContext = CGLayerGetContext(offscreenLayer);
Then I could do my updating off-screen into the CGContext and draw the CGLayer into my UIView in the UIView's drawRect: method, like so:
CGContextDrawLayerAtPoint(viewContext, CGPointZero, offscreenLayer);
The problem I am having is, where do I get my CGLayer from? My understanding is I have to make it using CGLayerCreateWithContext and supply a CGContext as a parameter from which it inherits most of it's properties. Obviously, the right context would be the context of the UIView, that I am getting with
CGContextRef viewContext = UIGraphicsGetCurrentContext();
but if I am not mistaken, I can only get that within the drawRect: method and it is not valid to assume that the context I am given there will be the same one next time the method is called, i.e. I can only use that CGContext locally within the method.
So, how can I get a CGContext that I can use to initialise my CGLayer to create an offscreen CGContext to draw into and then draw the entire layer back into my UIView's CGContext?
PS: While you're at it; if anything above does not make sense or is not sane, please let me know. I am just starting to get my head around Quartz 2D.
First of all, if you are doing it from in an iOS environment, I think you are right. The documentation clearly said that the only way to obtain a CGContextRef is by
CGContextRef ctx = UIGraphicGetCurrentContext();
Then you use that context for creating the CGLayer with
CGLayerRef layer = CGLayerCreateWithContext(ctx, (CGSize){0,0}, NULL);
And if you want to draw on that layer, you have to draw it with the context you get from the layer. (It is somewhat different from the context you passed in earlier to create the CGLayer). Im guessing the CGLayerCreateWithContext saves the information it can get from the context passed in, but not everything. (One of the example is the ColorSpace information, you have to re-specify when you fill something with the context from CGLayer).
You can get the CGLayer context reference from the CGLayerGetContext() function and use that to draw.
CGContextRef layerCtx = CGLayerGetContext(layer);
CGContextBeginPath(layerCtx);
CGContextMoveToPoint(layerCtx, -10, 10);
CGContextAddLineToPoint(layerCtx, 100, 10);
CGContextAddLineToPoint(layerCtx, 100, 100);
CGContextClosePath(layerCtx);
One point that I found out is when you draw something offscreen, it automatically clips the thing offscreen. (make sense, so it doesnt draw things that is not seen) but when you move the layer (using the matrix transformation). The clipped path is not showing (missing).
One last thing, if you save the reference to a layer into a variable and later on you want to draw it, you can use CGContextDrawLayerAtPoint() method like
CGContextDrawLayerAtPoint(ctx, (CGPoint) {newPointX, newPointY}, layer);
It will sort of "stampt" or "draw" the layer at that newPointX and new PointY coordinate.
I hope that answer your question, if its not please let me know.

How to keep the previous drawing when using Quartz on the iPhone?

I want to draw a simple line on the iPhone by touching and dragging across the screen. I managed to do that by subclassing UIView and change the default drawRect: method. At the same time in my viewcontroller I detect the touch event and call the [myView setNeedsDisplay] when necessary. The problem is that when I try to draw the second line the previous line disappears. Is there a way to keep the previous line on the screen?
Any input will be very much appreciated.
The usual method is to use CGBitmapContextCreate(). Create it in -init/-init-WithFrame:/etc and call CGContextRelease() in -dealloc. I'm not sure how you handle the 2x scale of the "retina display" with CGBitmapContextCreate().
(UIGraphicsBeginImageContext() is easier, but it might not be safe to do UIGraphicsBeginImageContext(); myContext = CFRetain(UIGraphicsGetCurrentContext()); UIGraphicsEndImageContext();.)
Then do something like
#import <QuartzCore/CALayer.h>
-(void)displayLayer:(CALayer*)layer
{
UIGraphicsPushContext(mycontext);
... Update the bitmap context by drawing a line...
UIGraphicsPopContext();
CGImageRef cgImage = CGBitmapContextCreateImage(mycontext);
layer.contents = (id)cgImage;
CFRelease(cgImage);
}
I've used -displayLayer: (a CALayer delegate function; a UIView is its layer's delegate) instead of -drawRect: for efficiency: if you use -drawRect:, CALayer creates a second context (and thus a second copy of the bitmap).
Alternatively, you might have luck with CGLayer. I've never seen a reason to use it instead of a bitmap context; it might be more efficient in some cases.
Alternatively, you might get away with setting self.clearsContextBeforeDrawing = NO, but this is very likely to break. UIKit (or, more accurately, CoreAnimation) expects you to draw the whole view contained in the clip rect (that's the "rect" argument of -drawRect:; it's not guaranteed to be the bounds). If your view goes offscreen, CoreAnimation might decide that it wants the memory back. Or CoreAnimation might only draw the part of the view that's on-screen. Or CoreAnimation might do double-buffered drawing, causing your view to appear to flip between two states.
If you use drawRect: to draw, then you need to draw the whole area. So you need to store not only the data for the latest part but everything.
As an alternative, you might draw directly into a bitmap, or generate dynamically subviews for your lines (makes only sense for very limited drawing (i.e. some few vector-based stuff).

How to reset the context to the original rectangle after clipping it for drawing?

I try to draw a sequence of pattern images (different repeated patterns in one view).
So what I did is this, in a loop:
CGContextRef context = UIGraphicsGetCurrentContext();
// clip to the drawing rectangle to draw the pattern for this portion of the view
CGContextClipToRect(context, drawingRect);
// the first call here works fine... but for the next nothing will be drawn
CGContextDrawTiledImage(context, CGRectMake(0, 0, 2, 31), [img CGImage]);
I think that after I've clipped the context to draw the pattern in the specific rectangle, I cut out a snippet from the big canvas and the next time, my canvas is gone. can't cut out another snippet. So I must reset that clipping somehow in order to be able to draw another pattern again somewhere else?
Edit: In the documentation I found this:
CGContextClip: "... Therefore, to
re-enlarge the paintable area by
restoring the clipping path to a prior
state, you must save the graphics
state before you clip and restore the
graphics state after you’ve completed
any clipped drawing. ..."
Well then, how to store the graphics state before clipping and how to restore it?
The functions you are looking for are:
CGContextSaveGState(context);
and
CGContextRestoreGState(context);

Moving CGPaths by x,y pixels?

Is there a way to move a path on screen by (x,y) pixels using directly a CGPathRef instead of walking through its points and lines again in drawRect method? I want to be reusing my old CGPathRef when I want to move it on screen instead of recreating it with new pixels.
Depending on your needs, you could just translate the drawing context via CGContextTranslateCTM before drawing your path (then restore the old context, either with push/popping contexts, or inverting the translation). You might also like CGPathApply, which will call a function for every path element in the path (so you can translate the points by hand).