iOS drawing arcs not working as expected - ios

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.

Related

Get real frame after affine transformation

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

iOS graphics appearing from upper left corner on custom control

I have been making a circular control and i am doing fine, except that the graphics appears from upper left corner when i do the render first time.
The whole control is subclassed UIControl, with custom CALayer which does rendering of a circle.
This is the code that renders the circle:
- (void) drawInContext:(CGContextRef)ctx{
id modelLayer = [self modelLayer];
CGFloat radius = [[modelLayer valueForKey:DXCircularControlLayerPropertyNameRadius] floatValue];
CGFloat lineWidth = [[modelLayer valueForKey:DXCircularControlLayerPropertyNameLineWidth] floatValue];
//NSLog(#"%s, angle: %6.2f, radius: %6.2f, angle_m: %6.2f, radius_m: %6.2f", __func__, self.circleAngle, self.circleRadius, [[modelLayer valueForKey:#"circleAngle"] floatValue], [[modelLayer valueForKey:#"circleRadius"] floatValue]);
// draw circle arc up to target angle
CGRect frame = self.frame;
CGContextRef context = ctx;
CGContextSetShouldAntialias(context, YES);
CGContextSetAllowsAntialiasing(context, YES);
// draw thin circle
//CGContextSetLineWidth(context, <#CGFloat width#>)
// draw selection circle
CGContextSetLineWidth(context, lineWidth);
CGContextSetStrokeColorWithColor(context, [UIColor greenColor].CGColor);
CGContextAddArc(context, frame.size.width / 2.0f, frame.size.height / 2.0f, radius, 0.0f, self.circleAngle, 0);
CGContextStrokePath(context);
CGContextSetShouldAntialias(context, NO);
}
Here is the video of a problem.
If you watch carefully, you'll notice that rendering of circle somehow doesnt start centered. It skews itself from the upper left corner.
This only happens when doing the animation for the first time.
I know this can happen if one mistakes begin and end of animation commit blocks, but i dont use them here.
Just in case here is the link to the bitbucket repo of this control:
There is nothing wrong with the drawing method - you messed up a little bit setting the layer's frame ;-)
You are setting the frame for the circularControlLayer within your - (void) setAngle:(CGFloat)angle method. That means the frame is set for the first time when you animate the angle property - so the frame will be animated too. Set the frame within the - (void) commonInitDXCircularControlView method.
If you are creating custom layers, have a look at the [UIView layerClass] method. Using it will save you from trouble with bounds/frame management.

CGContextAddArc counterclockwise instead of clockwise

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);

Draw an Angle and fill it with gradient colors

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.

CGContextDrawRadialGradient For Non-Circular Glow

I'm able to use CGContextDrawRadialGradient to make a sphere that does an alpha fade to UIColor.clearColor and it works.
However, I'm trying to do this type of thing:
While placing some strategic spheres around makes for an interesting effect (similar to LED backlights in strategic places), I would love to get a true glow. How can I draw a glow around a rounded rectangle in drawRect?
You can create a glow effect around any path using CGContextSetShadowWithColor, but you don't get precise control over the appearance. In particular, the default shadow is fairly light:
And the only way I know of to make it darker is to draw it again over itself:
Not optimal, but it approximates what you want pretty well.
Those images were generated by the following drawRect:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGMutablePathRef path = CGPathCreateMutable();
int padding = 20;
CGPathMoveToPoint(path, NULL, padding, padding);
CGPathAddLineToPoint(path, NULL, rect.size.width - padding, rect.size.height / 2);
CGPathAddLineToPoint(path, NULL, padding, rect.size.height - padding);
CGContextSetShadowWithColor(context, CGSizeZero, 20, UIColor.redColor.CGColor);
CGContextSetFillColorWithColor(context, UIColor.blueColor.CGColor);
CGContextAddPath(context, path);
CGContextFillPath(context);
// CGContextAddPath(context, path);
// CGContextFillPath(context);
CGPathRelease(path);
}
One thing to bear in mind is that rendering fuzzy shadows is fairly expensive, which may or may not be a problem depending on how often your views are redrawn. If the shadows don't need to animate, consider rendering them to a UIImage once and just displaying the result in a UIImageView.

Resources