I'm having an issue when drawing an arc inside of my drawing function inside of a CALayer subclass. The implementation for that drawing function is as follows:
-(void)drawInContext:(CGContextRef)ctx
{
CGPoint center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);
CGFloat radius = MIN(center.x, center.y);
CGContextBeginPath(ctx);
CGContextAddArc(ctx, center.x, center.y, radius, DEG2RAD(0), DEG2RAD(90), YES);
CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor);
CGContextSetLineWidth(ctx, 5);
CGContextDrawPath(ctx, kCGPathStroke);
}
Nothing too groundbreaking here, but the weird thing is that it's drawing the arc counterclockwise even though I specified that it shoud be clockwise. Conversely, when I specify NO for the clockwise parameter, it draws the arc clockwise. I did some research into flipped coordinate systems and figured that might be what the issue here is, but I didn't want to just hardcode the opposite bool parameter of what I meant. Any suggestions on how to fix this?
Thanks!
This is indeed due to the flipped coordinate system of UIKit vs. Quartz. From the docs for CGContext:
The clockwise parameter determines the direction in which the arc is created; the actual direction of the final path is dependent on the current transformation matrix of the graphics context. For example, on iOS, a UIView flips the Y-coordinate by scaling the Y values by -1. In a flipped coordinate system, specifying a clockwise arc results in a counterclockwise arc after the transformation is applied.
You can alleviate this in your code by using the transformation matrix to flip your context:
CGContextTranslateCTM(ctx, 0.0, self.bounds.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
You probably want to flip it back when you are finished with your drawing i.e.
CGContextSaveGState(ctx);
CGContextTranslateCTM(ctx, 0.0, self.bounds.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
// Draw...
CGContextRestoreGState(ctx);
Related
The goal is to get real frames in pdf page to identify the searched string, I am using PDFKitten lib to highlight the text that was searched and trying to figure out how to get frames for highlighted text. The core method is next:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
CGContextSetFillColorWithColor(ctx, [[UIColor whiteColor] CGColor]);
CGContextFillRect(ctx, layer.bounds);
// Flip the coordinate system
CGContextTranslateCTM(ctx, 0.0, layer.bounds.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
// Transform coordinate system to match PDF
NSInteger rotationAngle = CGPDFPageGetRotationAngle(pdfPage);
CGAffineTransform transform = CGPDFPageGetDrawingTransform(pdfPage, kCGPDFCropBox, layer.bounds, -rotationAngle, YES);
CGContextConcatCTM(ctx, transform);
CGContextDrawPDFPage(ctx, pdfPage);
if (self.keyword)
{
CGContextSetFillColorWithColor(ctx, [[UIColor yellowColor] CGColor]);
CGContextSetBlendMode(ctx, kCGBlendModeMultiply);
for (Selection *s in self.selections)
{
NSLog(#"layer.bounds = %f, %f, %f, %f", layer.bounds.origin.x, layer.bounds.origin.y, layer.bounds.size.width, layer.bounds.size.height);
CGContextSaveGState(ctx);
CGContextConcatCTM(ctx, s.transform);
NSLog(#"s.frame = %f, %f, %f, %f", s.frame.origin.x, s.frame.origin.y, s.frame.size.width, s.frame.size.height);
CGContextFillRect(ctx, s.frame);
CGContextRestoreGState(ctx);
}
}
}
Size of layer is (612.000000, 792.000000), but the size of s.frame is (3.110400, 1.107000). How can I get real frame from rect that is filled yellow?
As Matt says, a view/layer's frame property is not valid unless the transform is the identity transform.
If you want to transform some rectangle using a transform then the CGRect structure isn't useful, since a CGRect specifies an origin and a size, and assumes that the other 3 points of the rect are shifted right/down from the origin.
In order to create a transformed rectangle you need to build 4 points for the upper left, upper right, lower left, and lower right points of the untransformed frame rectangle, and then apply the transform to those points, before applying the transform to the view.
See the function CGPoint CGPointApplyAffineTransform(CGPoint point, CGAffineTransform t) to apply a CGAffineTransform to a point.
Once you've done that you could use the transformed points to build a bezier path containing a polygon that is your transformed rectangle. (It may or may not be a rectangle after transformation, and the only sure-fire way to represent it is as 4 points that describe a quadralateral.)
Please use bounds property. Also you have to use bounds when create custom layout. It's transform free.
frame it's rectangle which defines the size and position of the view in its superlayer’s coordinate system. bounds it's rectangle defines the size and position of the layer in its itself coordinate system.
https://developer.apple.com/reference/quartzcore/calayer/1410915-bounds
Below is the code which draws the Arc from M_PI/2 to M_PI.
CGContextRef contet=UIGraphicsGetCurrentContext();
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddArc(path, NULL, CGRectGetMidX(rect), CGRectGetMidY(rect), CGRectGetMidX(rect), M_PI/2.0,M_PI, YES);
CGContextAddPath(contet, path);
CGContextSetStrokeColorWithColor(contet, [UIColor redColor].CGColor);
CGContextStrokePath(content);
Below is the drawing of the above code.
I was expecting it to draw it in the empty or missing part , as its clockwise. I referred to this answer Why does giving addArcWithCenter a startAngle of 0 degrees make it start at 90 degrees? but, I don't know where I am wrong.
Because,in Core Graphics,the origin is at left/bottom
Flipping horizontally changes clockwise to anti clockwise and vice versa
You just need to change clockwise to NO to get what you want
You should just pay more attention to the method you used.
CGPathAddArc(path, NULL, CGRectGetMidX(rect), CGRectGetMidY(rect), CGRectGetMidX(rect), M_PI/2.0,M_PI, NO);
Just change the last parameter to NO, you will get what you want, it can control the drawing direction.
I'm trying to draw a simple arc with Quartz Core but I'm not getting the expected result.
My arc is a basic 0 degrees to 90 degree arc (counter-clockwise direction).
I have this code here:
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(ctx, self.strokeColor.CGColor);
CGContextMoveToPoint(ctx, self.center.x, self.center.y);
CGFloat radius = self.bounds.size.width / 2.0;
CGContextAddArc(ctx, self.center.x, self.center.y, radius, [MathTools degreesToRadians:0], [MathTools degreesToRadians:90], 0);
CGContextStrokePath(ctx);
}
Note: MathTools is just a convenient class I create for converting degrees to radians and vice versa, the implementation for degreesToRadians: is:
+(CGFloat)degreesToRadians:(CGFloat)degrees
{
return degrees * M_PI / 180.0;
}
But instead of seeing a white arc inside my purple circle, all I see is white dash:
I'm trying to get it to look like this:
Edit
Based on answer given by rmaddy, my new code looks like this:
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(ctx, self.strokeColor.CGColor);
CGContextSetLineWidth(ctx, self.strokeWidth);
CGFloat radius = self.bounds.size.width / 2.0 - self.strokeWidth;
CGContextAddArc(ctx, CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds), radius, [MathTools degreesToRadians:0], [MathTools degreesToRadians:-90], 1);
CGContextStrokePath(ctx);
}
Not sure if this helps anyone else but based on what I see here, it seems Apple's angle positive and negative direction is not the same as mathematics. From memory, mathematic +90 degrees is anti-clockwise, but Apple's +90 degrees seems like clockwise.
One critical issue I see is your use of self.center. Your intent is to move to the center of the view but self.center is relative to the view's frame, not its bounds.
Everything in drawRect: needs to be relative to the view's bounds, not its frame.
Change this line:
CGContextMoveToPoint(ctx, self.center.x, self.center.y);
to:
CGContextMoveToPoint(ctx, CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
Make a similar change to the call to CGContextAddArc.
BTW - the only time self.center would work for this case is if the view's origin is at 0, 0. With any other origin, self.center will give you a problem for this case.
First off, here's an image of my current situation.
To accomplish this I am using a subclassed UIView with the following method:
-(void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextBeginPath(ctx);
CGContextMoveToPoint (ctx, CGRectGetMinX(rect), CGRectGetMinY(rect)); // top left
CGContextAddLineToPoint(ctx, CGRectGetMaxX(rect), CGRectGetMidY(rect)); // mid right
CGContextAddLineToPoint(ctx, CGRectGetMinX(rect), CGRectGetMaxY(rect)); // bottom left
CGContextClosePath(ctx);
CGContextSetRGBFillColor(ctx, 0, 1, 0, 1);
CGContextFillPath(ctx);
}
My ultimate goal is to clip the colored regions so that only the neon green remains. (I'll flip the colors later). I want it to look like a pie chart. I'm guessing there's a way to clip the colored corners out but I have no clue. CGContextClip(ctx) doesn't work.
I am using the Pixate framework as well. If anyone knows a way to accomplish a triangle shape with Pixate that would be even better.
The easiest way to clip a UIView to a path is to create a CAShapeLayer, add a path to the layer, and add the shape layer as a mask of your view's layer.
Or with a CGPathRef, essentially the same thing but at core foundation level
eg..
{
// before existing drawing code..
CGMutablePathRef clipTriangle = CGPathCreateMutable();
CGPathMoveToPoint (clipTriangle, nil, startX, startY);
CGPathAddLineToPoint(clipTriangle, nil, secondPointX, secondPointY);
CGPathAddLineToPoint(clipTriangle, nil, thirdPointX, thirdPointY);
CGPathCloseSubpath(clipTriangle);
CGContextSaveGstate(ctx);
CGContextAddPath(ctx, clipTriangle); /// this is the essential line I left out, we drew the triangle and forgot to use it, clipping to nothing instead, sorry mate
CGContextClip(ctx);
//draw stuff that you wanted clipped here...
CGContextRestoreGstate(ctx);
CGPathRelease(clippingTriangle); //CFstuff still needs manual memory management regardless of ARC
}
I'm making a custom control and it was fine til my client says "I want it with gradients".
So here I am.
I need to draw an angle and fill it with gradient color. My previous code was this:
- (void)drawRect:(CGRect)rect{
CGContextBeginPath(context);
CGContextMoveToPoint(context, self.center.x, self.center.y);
CGContextAddArc(context, self.center.x, self.center.y, radius, radians(180), radians(minValue - 180), 0);
CGContextClosePath(context);
CGContextSetFillColorWithColor(context, gray);
CGContextFillPath(context);
}
With the CGContextAddArc() function I can draw my angle and paint it with CGContextSetFillColorWithColor() function, then close the path and voilá. But now I need to painted with gradients.
I know that maybe I should use CGContextDrawRadialGradient, but don't know how to use it with an arc.
I've been tried to figured out this with other question in StackOVerFlow and reading the doc, but I just can't get it.