I'm ultimately trying to create a gradient overlay to fade from 60% black to clear, to 40% black. The result has a lot of banding, so I'm just trying to get two colours working properly first.
In this first case, I'm trying a gradient from solid black to solid white which draws pretty much perfectly.
In the second case, I'm placing a gradient from 100% black to clear, against a solid gray background. The banding is really awful.
The code to make this gradient looks like this:
// Draw the overlay gradient
CGContextTranslateCTM(context, 0.0, size.height);
CGContextScaleCTM(context, 1.0, -1.0);
UIColor *firstColor = [UIColor colorWithWhite:0.0 alpha:1.0];
UIColor *secondColor = [UIColor clearColor];
CGContextSetBlendMode(context, kCGBlendModeSourceAtop);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
NSArray *coreGraphicsColors = #[(id)firstColor.CGColor, (id)secondColor.CGColor];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)coreGraphicsColors, 0);
static const CGFloat standardDegree = 270;
const CGFloat degree = (standardDegree * M_PI / 180);
const CGPoint center = CGPointMake(size.width / 2, size.height / 2);
const CGPoint startPoint = CGPointMake(center.x - cos(degree) * size.width / 2, center.y - sin(degree) * size.height / 2);
const CGPoint endPoint = CGPointMake(center.x + cos(degree) * size.width / 2, center.y + sin(degree) * size.height / 2);
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
Does anyone have some experience/tips with creating these gradients which are partly transparent? I've tried altering a few things like the positions and color space, all to no avail.
This might help you AlphaGradientView
Related
I need a radial gradient in the shape of an oval or ellipse and it seems like it CGContextDrawRadialGradient can only draw a perfect circle. I've been drawing to a square context then copying/drawing into a rectangular context.
Any better way to do this?
Thanks!
The only way I've found to do this is as Mark F suggested, but I think the answer needs an example to be easier to understand.
Draw an elliptical gradient in a view in iOS (and using ARC):
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
// Create gradient
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat locations[] = {0.0, 1.0};
UIColor *centerColor = [UIColor orangeColor];
UIColor *edgeColor = [UIColor purpleColor];
NSArray *colors = [NSArray arrayWithObjects:(__bridge id)centerColor.CGColor, (__bridge id)edgeColor.CGColor, nil];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);
// Scaling transformation and keeping track of the inverse
CGAffineTransform scaleT = CGAffineTransformMakeScale(2, 1.0);
CGAffineTransform invScaleT = CGAffineTransformInvert(scaleT);
// Extract the Sx and Sy elements from the inverse matrix
// (See the Quartz documentation for the math behind the matrices)
CGPoint invS = CGPointMake(invScaleT.a, invScaleT.d);
// Transform center and radius of gradient with the inverse
CGPoint center = CGPointMake((self.bounds.size.width / 2) * invS.x, (self.bounds.size.height / 2) * invS.y);
CGFloat radius = (self.bounds.size.width / 2) * invS.x;
// Draw the gradient with the scale transform on the context
CGContextScaleCTM(ctx, scaleT.a, scaleT.d);
CGContextDrawRadialGradient(ctx, gradient, center, 0, center, radius, kCGGradientDrawsBeforeStartLocation);
// Reset the context
CGContextScaleCTM(ctx, invS.x, invS.y);
// Continue to draw whatever else ...
// Clean up the memory used by Quartz
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
}
Put in a view with a black background you get:
You can change the transform of the context to draw an ellipse (for example, apply CGContextScaleCTM(context, 2.0, 1.0) just before calling CGContextDrawRadialGradient () to draw an elliptical gradient that's twice as wide as it is high). Just remember to apply the inverse transform to your start and end points, though.
It should be very simple to create a black to white radial gradient using Core Graphics, but even after many tries I am not able to figure where I am going wrong.
I want something like this:
This is my code:
CGFloat radius = shapeRect.size.width/2.0;
CGPoint center = CGPointMake(shapeRect.origin.x+shapeRect.size.width/2, shapeRect.origin.y+shapeRect.size.height/2);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
CGContextRef context = CGBitmapContextCreate(NULL, shapeRect.size.width, shapeRect.size.height, 8, shapeRect.size.width*4, colorSpace, kCGImageAlphaNone);
CGFloat components[] = {0.0, 1.0};
CGFloat locations[] = {0.0, 1.0};
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, 2);
CGContextDrawRadialGradient(context, gradient, center, 10, center, radius, 0);
CGImageRef img = CGBitmapContextCreateImage(context);
[maskedView setImage:[UIImage imageWithCGImage:img]];
CGGradientRelease(gradient);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
ShapeRect here has self.view bounds.
I am getting a whole black screen with no gradient as output.
I am using iPhone 7 simulator.
Your suggestions can save my day
Thank you.
Please check this snippet. You can change components and locations values to get what you want and to understand how the drawing works. Refer to CGGradientCreateWithColorComponentsdescription
#import "TestView.h"
#implementation TestView {
CGGradientRef _gradient;
}
-(void)drawRect:(CGRect)rect
{
CGFloat radius = self.frame.size.width / 2.0;
CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
CGContextRef context = UIGraphicsGetCurrentContext();
CGGradientRef gradient = [self createGradient];
CGContextDrawRadialGradient(context, gradient, center, 10, center, radius, kCGGradientDrawsBeforeStartLocation);
}
-(CGGradientRef)createGradient
{
if (!_gradient) {
CGFloat components[] = { 0.0, 1.0, 0.5, 0.5, 1.0, 0.3 };
CGFloat locations[] = { 0.0, 0.6, 1.0 };
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
_gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, 3);
CGColorSpaceRelease(colorSpace);
}
return _gradient;
}
- (void)layoutSubviews
{
self.backgroundColor = [UIColor whiteColor];
self.layer.borderColor = [UIColor lightGrayColor].CGColor;
self.layer.borderWidth = 1.;
}
#end
Result:
I'm trying to create an animated pie chart in iOS that acts basically like this:
In a nutshell, it starts as a grey circle, and as the animation progresses the arrow moves around the circle until it gets to the percentage I specify.
I've used the sample code that Zachary Waldowski posted in this SO question:
Animated CAShapeLayer Pie
That's gotten me to the point where I can create the basic animation. The maroon pie piece grows to hit the correct size. What I'm struggling with is how to map the arrow to the animation so that it gets dragged along as the pie piece grows.
Any thoughts on how I can accomplish this?
Okay, I've found the solution.
Building on the work Zachary Waldowski created (see my original post), I was able to do the entire thing in a CALayer.
In a nutshell, I draw one outer circle in maroon, a smaller circle in light gray, a stroked path in white, then draw the triangle for the tip of the arrow by hand.
Here's the relevant section of code that does the magic:
- (void)drawInContext:(CGContextRef)context {
CGRect circleRect = CGRectInset(self.bounds, 1, 1);
CGFloat startAngle = -M_PI / 2;
CGFloat endAngle = self.progress * 2 * M_PI + startAngle;
CGColorRef outerPieColor = [[UIColor colorWithRed: 137.0 / 255.0 green: 12.0 / 255.0 blue: 88.0 / 255.0 alpha: 1.0] CGColor];
CGColorRef innerPieColor = [[UIColor colorWithRed: 235.0 / 255.0 green: 214.0 / 255.0 blue: 227.0 / 255.0 alpha: 1.0] CGColor];
CGColorRef arrowColor = [[UIColor whiteColor] CGColor];
// Draw outer pie
CGFloat outerRadius = CGRectGetMidX(circleRect);
CGPoint center = CGPointMake(CGRectGetMidX(circleRect), CGRectGetMidY(circleRect));
CGContextSetFillColorWithColor(context, outerPieColor);
CGContextMoveToPoint(context, center.x, center.y);
CGContextAddArc(context, center.x, center.y, outerRadius, startAngle, endAngle, 0);
CGContextClosePath(context);
CGContextFillPath(context);
// Draw inner pie
CGFloat innerRadius = CGRectGetMidX(circleRect) * 0.45;
CGContextSetFillColorWithColor(context, innerPieColor);
CGContextMoveToPoint(context, center.x, center.y);
CGContextAddArc(context, center.x, center.y, innerRadius, startAngle, endAngle, 0);
CGContextClosePath(context);
CGContextFillPath(context);
// Draw the White Line
CGFloat lineRadius = CGRectGetMidX(circleRect) * 0.72;
CGFloat arrowWidth = 0.35;
CGContextSetStrokeColorWithColor(context, arrowColor);
CGContextSetFillColorWithColor(context, arrowColor);
CGMutablePathRef path = CGPathCreateMutable();
CGContextSetLineWidth(context, 16);
CGFloat lineEndAngle = ((endAngle - startAngle) >= arrowWidth) ? endAngle - arrowWidth : endAngle;
CGPathAddArc(path, NULL, center.x, center.y, lineRadius, startAngle, lineEndAngle, 0);
CGContextAddPath(context, path);
CGContextStrokePath(context);
// Draw the Triangle pointer
CGFloat arrowStartAngle = lineEndAngle - 0.01;
CGFloat arrowOuterRadius = CGRectGetMidX(circleRect) * 0.90;
CGFloat arrowInnerRadius = CGRectGetMidX(circleRect) * 0.54;
CGFloat arrowX = center.x + (arrowOuterRadius * cosf(arrowStartAngle));
CGFloat arrowY = center.y + (arrowOuterRadius * sinf(arrowStartAngle));
CGContextMoveToPoint (context, arrowX, arrowY); // top corner
arrowX = center.x + (arrowInnerRadius * cosf(arrowStartAngle));
arrowY = center.y + (arrowInnerRadius * sinf(arrowStartAngle));
CGContextAddLineToPoint(context, arrowX, arrowY); // bottom corner
arrowX = center.x + (lineRadius * cosf(endAngle));
arrowY = center.y + (lineRadius * sinf(endAngle));
CGContextAddLineToPoint(context, arrowX, arrowY); // point
CGContextClosePath(context);
CGContextFillPath(context);
[super drawInContext: context];
}
Have the pie with the arrow as an image.
In every step, draw this image, rotating it a little. Draw the gray pie over the image, choosing correct angles for the image rotation.
I have a simple drawing routine that allows the user to draw two enclosed ovals on the screen. I want to fill the ovals with a gradient, using the inner oval to represent the "percentage" of the gradient. i.e. Th gradient will smoothly transition between the outside oval to the inside oval.
I have the interactive drawing working fine, now I just need to fill with the gradient.
Any thoughts? The docs only talk about perfectly circular gradients, not ovals.
_mike
I do not know whether it is possible oval gradient . But you can transform circle to oval. The idea is to draw a circle in transformed coordinate system.
Sample of code:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextScaleCTM(context, 1.0, 0.5);
CGGradientRef gradient;
CGColorSpaceRef colorspace;
CGFloat locations[2] = { 0.0, 1.0};
NSArray *colors = #[(id)[UIColor whiteColor].CGColor, (id)[UIColor blueColor].CGColor];
colorspace = CGColorSpaceCreateDeviceRGB();
gradient = CGGradientCreateWithColors(colorspace, (CFArrayRef)colors, locations);
CGPoint startPoint, endPoint;
CGFloat startRadius, endRadius;
startPoint.x = 180;
startPoint.y = 180;
endPoint.x = 180;
endPoint.y = 180;
startRadius = 0;
endRadius = 100;
CGContextDrawRadialGradient (context, gradient, startPoint, startRadius, endPoint, endRadius, 0);
CGContextRestoreGState(context);
}
Result of running code:
I am using radial gradient to draw circle. It is working fine but now I want to set border color and border width to that. I tried it but it was not working.
Here is my code,
CGGradientRef gradient;
CGColorSpaceRef colorSpace;
CGFloat locations[] = {0.0,1.0};
CGFloat components[] = { red2,green2,blue2,1.0,red1,green1,blue1,1.0 };
colorSpace = CGColorSpaceCreateDeviceRGB();
gradient = CGGradientCreateWithColorComponents(colorSpace,components,locations,
sizeof(locations)/sizeof(CGFloat));
CGPoint start = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2 ), end = CGPointMake(self.bounds.size.width/2 , self.bounds.size.height/2);
CGFloat startRadius = 0.0, endRadius = radius;
CGContextDrawRadialGradient(contextRef,gradient,start,startRadius,end,endRadius,0);
CGContextSetLineWidth(contextRef, 5.0);
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
How can I set border color and width to it?
First add QuartzCore Framework and then try this code...
[[Firstbtn layer]setBorderColor:[[UIColor blackColor]CGColor]];
[[self.Firstbtn layer]setBorderWidth:2.3];
[[self.Firstbtn layer]setCornerRadius:15];