The purpose of this post is to demystify as far as possible the process of translating and rotating a CGContext.
Obtaining a reference to a CGContext
Before anything else, it is necessary to obtain a reference to the current context from the drawRect() method of a UIView subclass.
override func drawRect(rect:CGRect) { // obtain context let ctx = UIGraphicsGetCurrentContext() }
Drawing a circle
With the context available to us, let's suppose we draw a circle at the centre of it. The black square represents the context:// Decide on radius let rad = CGRectGetWidth(rect)/3.5 // End angle will be 2*pi for any circle that begins at 0 let endAngle = CGFloat(2*M_PI) // We could use CGContextAddEllipseInRect to draw a circle instead CGContextAddArc(ctx, CGRectGetMidX(rect), CGRectGetMidY(rect), rad, 0, endAngle, 1) // set stroke color CGContextSetStrokeColorWithColor(ctx,UIColor.whiteColor().CGColor) // Set line width CGContextSetLineWidth(ctx, 4.0) CGContextStrokePath(ctx)
Note: The code could be simpler, but I want to think here in terms of origins rather than rectangles and so for consistency I've used the CGContextAddArc() method.
Saving the context state
Before any transformation takes place it is important to save the current context state, so that it can be returned to.
CGContextSaveGState(ctx)
Translating the origin of the context
Now the origin of the context can be translated to the origin of the circle like so:
In code this translation is the equivalent to writing
CGContextTranslateCTM(ctx, CGRectGetMidX(rect), CGRectGetMidY(rect))Note: the circle doesn't move with the context, all that has already been drawn stays where it is.
Rotating the context
Now the context has been translated to a new position it can also be rotated. The rotation happens around the origin of the context.In Swift the rotation would look like this:
CGContextRotateCTM(ctx, CGFloat(M_PI*45/180))
Again the pre-exisiting drawing does not change.
Drawing on the translated and rotated context
Let's suppose we now draw a line from (0, 0) to (radius, 0) where "radius" is the radius of the circle.
The Swift code looks like this:
// create a mutable path let path = CGPathCreateMutable() // move to the starting point of the path CGPathMoveToPoint(path, nil, 0, 0) // add a line the length of the radius to path CGPathAddLineToPoint(path, nil, rad, 0) // add the path to the (translated and rotated) context CGContextAddPath(ctx, path) // set line width CGContextSetLineWidth(ctx, 4) // set line color CGContextSetStrokeColorWithColor(ctx,UIColor.whiteColor().CGColor) // stroke path CGContextStrokePath(ctx)
Restoring the context
Restoration of the context in code is as simple as the saving of it.CGContextRestoreGState(ctx)Once the context is restored back to its original state, this is how things look:
The new line stays exactly where it was drawn and any new drawing uses as its origin the original (0, 0) of the context.
Conclusion
I'm not going to discuss pros and cons of shifting the graphics context around, although Apple does state: "The advantage of modifying the graphics context (as opposed to the path object itself) is that you can easily undo the transformation by saving and restoring the graphics state."
Here I simply wanted to explain what happens and to lay some foundations for a future post. Full code can be found here for you to copy and paste into an initial view controller.
why are u using CGRectGetWidth for radius??
ReplyDeleteit was just a way of ensuring that the circle fits within the rect.
DeletePlease Can you provide the link of the final project!
ReplyDeleteYou have provided an nice article, Thank you very much for this one. And i hope this will be useful for many people.. and i am waiting for your next post keep on updating these kinds of knowledgeable things...
ReplyDeleteMobile App Development Company in Chennai
Android app Development Company in Chennai
ios app development Company in Chennai
Thank you for the clear and detailed explanation! Naturally Swift 4 has done a couple of renames:
ReplyDeletectx.saveGState()
ctx.rotate(by: -GraphView.DEG_90)
ctx.translateBy(x: 0, y: height)
ctx.restoreGState()
Thanks Kevin, I think this post will be worth revisiting at some point. I see many places where I could've been clearer in my explanations, so I'll replace with Swift 4 code then.
DeleteQuestion, the call to CGContext.rotate(by:) seems to contradict the doc which says positive values for the angle rotate counter-clockwise?
Delete