I want to display a complex UI element as a kinda donut with sectors, so each sector could be represented as a single UIView.
Is there any way to transform the rectangular UIView into an arc to fit into donut contour? Or that's impossible?
I'm a total iOS noob and would be very appreciative if you pointed me to the right documentation.
UIView's are always rectangular unless you apply an affine transform to it (but I still don't think you can get an arc shape). I think your best bet would be to draw the arc inside the UIView by subclassing the UIView and implementing drawRect or using CAShapeLayer.
Without knowing more about exactly what your trying to do, I'm going to go out on a limb and suggest you use CGDrawing methods to draw your donut segments in a rectangular view.
To do that you would use the following in particular:
CGPathAddArc()
CGPathAddLineToPoint()
CGPathMoveToPoint()
This code draws filled arcs:
-(void)drawFilledArc:(CGContextRef)context innerRadius:(CGFloat)rIn outterRadius (CGFloat)rOut
startAngle:(double)startAng endAngle:(double)endAng drawColor:(UIColor *)color
{
float x1, y1;
CGFloat centerX = originX;
CGFloat centerY = originY;
CGContextSaveGState(context);
CGMutablePathRef path = CGPathCreateMutable();
CGContextSetLineWidth(context, lineWidth);
getChartWheelCoord(centerX, centerY, startAng, rIn, &x1, &y1);
CGPathMoveToPoint(path, NULL, x1, y1);
getChartWheelCoord(centerX, centerY, startAng, rOut, &x1, &y1);
CGPathAddLineToPoint(path, NULL, x1, y1);
CGPathAddArc(path, NULL, centerX, centerY, rOut, radians(-startAng), radians(-endAng), YES);
getChartWheelCoord(centerX, centerY, endAng, rIn, &x1, &y1);
CGPathAddLineToPoint(path, NULL, x1, y1);
CGPathAddArc(path, NULL, centerX, centerY, rIn, radians(-endAng), radians(-startAng), NO);
CGPathCloseSubpath(path);
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextAddPath(context, path);
CGContextDrawPath(context, kCGPathFillStroke);
CGPathRelease(path);
CGContextRestoreGState(context);
}
Well You can achieve what you want in following ways.
1 If your UI Element has not to be answerable to Click or touch Events , then you can draw it
Using Core-Graphics.
2 If your UI Element has to respond to user-interaction events then you can create a UIView
and add as many custom-buttons as you want as subview to the UIview along with the arc shaped or sector shaped images(Images for Custom Buttons).
Related
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 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.
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 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);
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.