CGContextDrawRadialGradient For Non-Circular Glow - ios

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.

Related

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.

CGContextClip Antialiasing issue

I am drawing a shape in a UIView drawRect function that involves clipping a path, and then adding coloured blocks behind so that the colors have the shape of the clipped path. However, for some reason the lines of the path are not coming out smoothly; its if the antialiasing isn't working properly.
CGContextSetStrokeColorWithColor(context, [UIColor clearColor].CGColor);
CGContextSetLineWidth(context, 1.0);
CGContextSetShouldAntialias(context, true);
CGContextSetAllowsAntialiasing(context, true);
CGContextBeginPath (context);
CGContextMoveToPoint(context, xStart, yStart);
for (int i=0; i<points.count; i++) {
CGContextAddLineToPoint(context, xPoint, yPoint);
}
}
CGContextAddLineToPoint(context, xStart, yStart );
CGContextClip(context);
CGRect colorRect = CGRectMake(0, 0 , rectWidth, rectHeight);
CGContextSetFillColorWithColor(context, blockColor.CGColor);
CGContextFillRect(context, colorRect);
CGContextRestoreGState(context);
The result should have smooth lines, but it comes out jagged with visible pixels as in this image:
Any idea what the problem is and how to fix it?
Thanks in advance
Okey. So, there is no problem in your code. The problem is that antialiasing works a little different. You drawing a vertical slopes expecting that edge of resulting histogram will be smoothed. But actually antialiasing doesn't smooth the resulting figure. It works with path elements (curves, lines) one by one. So, for example if you draw a circle, its edges will be smoothed.
There is a simple solution of your problem: just create a curve, enveloping the histogram. Add it to your context. It will look more smooth.
Sorry for bad english.

Clipping a custom UIView to a triangle

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
}

display UIView as an arc

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

CGPath with outline

I am trying to draw a CGPath that has a stroke for it's stroke.
Basically I want a draw a line using CGPath. I then want to go back and draw lines on both sides of the last CGPath giving it the effect that it is outlines.
This line can bend and turn in any way but I always need the two lines on the outside to follow.
EDIT: I need to be able to make the middle of the line transparent but the outlines solid black.
Use CGPathCreateCopyByStrokingPath to create a new path by stroking your old path at some width. Then draw your new path using kCGPathFillStroke.
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 50, 50);
CGPathAddLineToPoint(path, NULL, 200, 200);
CGPathRef thickPath = CGPathCreateCopyByStrokingPath(path, NULL, 10, kCGLineCapButt, kCGLineJoinBevel, 0);
CGContextAddPath(context, thickPath);
CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor);
CGContextSetLineWidth(context, 3);
CGContextDrawPath(context, kCGPathFillStroke);
CGPathRelease(thickPath);
CGPathRelease(path);
}
The simplest solution would just be to stroke the path twice. First with black at a bigger stroke width and then stroke the same path again using the blue color with a slightly smaller stroke width.
Edit:
If I remember correctly you can use CGPathCreateCopyByStrokingPath(...) to create a new path that you then can both stroke and fill. Then you could use semi-transparent colors.
From the documentation:
CGPathCreateCopyByStrokingPath
Creates a stroked copy of another path.
CGPathRef CGPathCreateCopyByStrokingPath(
CGPathRef path,
const CGAffineTransform *transform,
CGFloat lineWidth,
CGLineCap lineCap,
CGLineJoin lineJoin,
CGFloat miterLimit
);
Parameters
path
The path to copy.
transform
A pointer to an affine transformation matrix, or NULL if no transformation is needed. If specified, Quartz applies the transformation to elements of the converted path before adding them to the new path.
lineWidth
The line width to use, in user space units. The value must be greater than 0.
lineCap
A line cap style constant—kCGLineCapButt (the default), kCGLineCapRound, or kCGLineCapSquare. See “CGLineCap”.
lineJoin
A line join value—kCGLineJoinMiter (the default), kCGLineJoinRound, or kCGLineJoinBevel. See “CGLineJoin”.
miterLimit
The miter limit to use.

Resources