How to draw the gradient in iOS - ios

I want to draw the Circular progress view with gradient as image :
Design Image
I tried the below code but the result color is not correct. This is my code:
//draw gradient
CGFloat colors [] = {
1.0, 0.7,
0.2, 0.4
};
CGFloat locations[] = { 0.0, 1.0 };
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceGray(); // gray colors want gray color space
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, colors, locations, 2);
CGColorSpaceRelease(baseSpace), baseSpace = NULL;
CGContextSaveGState(context);
CGContextAddPath(context, progressPath);
CGContextClip(context);
CGRect boundingBox = CGPathGetBoundingBox(progressPath);
CGPoint gradientStart = CGPointMake(0.0, CGRectGetMinY(boundingBox));
CGPoint gradientEnd = CGPointMake(1.0, CGRectGetMaxY(boundingBox));
CGContextDrawLinearGradient(context, gradient, gradientStart, gradientEnd, 0);
CGGradientRelease(gradient), gradient = NULL;
CGContextRestoreGState(context);
The progressPath is the progress line with color :
[UIColor colorWithRed:21/255.0f green:182/255.0f blue:217/255.0f alpha:1.0]
UPDATE:
This is an image my code produced: Result Image
The result image is not the same the design, the color is not the same. I dont know why.
Please help me to correct this. Thanks in advance.

The kind of gradient you are looking for, where the color varies as a function of the angle around a center point, is called an angle gradient. Unfortunately, iOS does not have a built-in way to draw angle gradients, but you can do it using an open-source library such as paiv/AngleGradientLayer.

Working code:
- (void) setGradientWithFrame:(CGRect)frame
{
//create the gradient
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = frame;
//this example is white-black gradient, change it to the color you want..
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor whiteColor] CGColor], (id)[[UIColor blackColor] CGColor], nil];
gradient.cornerRadius = frame.size.height/2.0f;
[self.view.layer insertSublayer:gradient atIndex:0];
CALayer *whiteCircle = [CALayer layer];
whiteCircle.frame = CGRectMake(frame.origin.x + 10, frame.origin.y + 10, frame.size.width - 20, frame.size.height - 20);
whiteCircle.backgroundColor = [[UIColor whiteColor] CGColor];
whiteCircle.cornerRadius = whiteCircle.frame.size.width/2.0f;
[self.view.layer insertSublayer:whiteCircle atIndex:1];
CALayer *whitesquare = [CALayer layer];
whitesquare.frame = CGRectMake(50, frame.size.height - 20, 25, 25);
whitesquare.backgroundColor = [[UIColor whiteColor] CGColor];
whitesquare.transform = CATransform3DMakeRotation(-20.0 / 180.0 * M_PI, 0.0, 0.0, 1.0);
[self.view.layer insertSublayer:whitesquare atIndex:2];
}
to call the method:
[self setGradientWithFrame:CGRectMake(0, 0, 100, 100)];
PS. change the color to the color of gradient to the color you want:

Related

How to Convert CAShapeLayer to CAGradientLayer?

my code of making CAShapeLayer is
UIBezierPath *circle = [UIBezierPath bezierPathWithArcCenter:CGPointMake(75, 125)
radius:50
startAngle:0
endAngle:1.8 * M_PI
clockwise:YES];
CAShapeLayer *circleLayer = [CAShapeLayer layer];
[circleLayer setFrame:CGRectMake(200 - 50, 300 - 50, 100, 100)];
circleLayer.path = circle.CGPath;
circleLayer.bounds = CGPathGetBoundingBox(circle.CGPath);
circleLayer.strokeColor = [UIColor colorWithRed:0.4 green:1.0 blue:0.2 alpha:0.5].CGColor;
[circleLayer setFillColor:[UIColor clearColor].CGColor];
circleLayer.lineWidth = 3.0;
if ([circleLayer animationForKey:#"SpinAnimation"] == nil) {
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat: 2 * M_PI];
animation.duration = 2.0f;
animation.repeatCount = INFINITY;
animation.removedOnCompletion = NO;
[circleLayer addAnimation:animation forKey:#"SpinAnimation"];
}
[self.view.layer addSublayer:circleLayer];
I want to make CAGradientLayer instead of CAShapeLayer.
REASON: I need to use gradient color in layer, which is not possible in CAShapeLayer.
I want to use yellow to black gradient upon Cirle.
Image :
In my required output, color is fetched from background image, and put some black at the end, or opacity 0 within layer.
Any Idea, suggestion.
Thanks.
One quick and dirty trick is adding a gradient layer as a sublayer then put some more layers with the background colors same as the superview (in the original question, it's white).
For example:
Add a gradient layer (and set proper cornerRadius).
Add a smaller layer with white background color at the center of the gradient layer (this will create a ring).
Create a small segment of arc with white background color.
However, this may not be suitable views with background images.
To solve this, you can create a custom subclass of UIView and override the drawRect method.
Suppose there are instance variables: startColor, endColor, radius, strokeWidth, and mirrored.
The variable mirrored is used to control the position of the gap (cut off left side or right side) and the rotation direction of the animation (clockwise or counter-clockwise).
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(ctx, kCGBlendModeNormal);
CGFloat gradientColors[4 * 2];
extractColorComponents(_startColor, gradientColors, 0);
extractColorComponents(_endColor, gradientColors, 4);
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, gradientColors, NULL, 2);
CGColorSpaceRelease(baseSpace);
baseSpace = nil;
CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient);
gradient = nil;
// fill 'clear' color
CGContextSetFillColorWithColor(ctx, [UIColor clearColor].CGColor);
CGContextSetStrokeColorWithColor(ctx, [UIColor clearColor].CGColor);
CGContextSetBlendMode(ctx, kCGBlendModeClear);
CGFloat innerCircleRadius = _radius - _strokeWidth;
// fill an 'empty' hole inside the gradient part
CGContextFillEllipseInRect(ctx, (CGRect){
{_strokeWidth, _strokeWidth},
{innerCircleRadius * 2, innerCircleRadius * 2}
});
// fill an 'empty' segment of arc
CGFloat startAngle = _mirrored ? (0.9 * M_PI) : (-0.1 * M_PI);
CGFloat endAngle = startAngle + 0.2 * M_PI;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:(CGPoint){_radius, _radius}
radius:_radius - _strokeWidth * 0.5
startAngle:startAngle
endAngle:endAngle
clockwise:YES];
path.lineWidth = _strokeWidth;
CGContextAddPath(ctx, path.CGPath);
CGContextSetLineWidth(ctx, _strokeWidth + 2);
CGContextStrokePath(ctx);
CGContextSetBlendMode(ctx, kCGBlendModeNormal);
}
Here's the complete implementation of the view.
Final result:
Two rings:
You can add two rings in your viewDidLoad or somewhere else with the following code to achieve the desired effect:
- (void)viewDidLoad
{
[super viewDidLoad];
UIColor *startColor = [UIColor colorWithHue:0.02 saturation:0.74 brightness:0.91 alpha:1];
UIColor *endColor = [UIColor colorWithHue:0.57 saturation:0.76 brightness:0.86 alpha:1];
CGPoint center = CGPointMake(160, 200);
Spinner *outerSpinner = [[Spinner alloc] initWithCenter:center
radius:50
strokeWidth:3
startColor:startColor
endColor:endColor
mirrored:NO];
Spinner *innerSpinner = [[Spinner alloc] initWithCenter:center
radius:40
strokeWidth:3
startColor:startColor
endColor:endColor
mirrored:YES];
[self.view addSubview:outerSpinner];
[self.view addSubview:innerSpinner];
[outerSpinner startAnimating];
[innerSpinner startAnimating];
}
Result:
The mask property on CALayer uses the alpha component of the mask layer to determine what should be visible and not. Since both of your colors (black and white) are fully opaque (no transparency) the mask has no effect. To fix this you should change one of the colors to a clear color:
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor blackColor] CGColor],
(id)[[UIColor clearColor] CGColor], nil];
EDIT
class Colors {
let colorTop = UIColor(red: 192.0/255.0, green: 38.0/255.0, blue: 42.0/255.0, alpha: 1.0).CGColor
let colorBottom = UIColor(red: 35.0/255.0, green: 2.0/255.0, blue: 2.0/255.0, alpha: 1.0).CGColor
let gl: CAGradientLayer
init() {
gl = CAGradientLayer()
gl.colors = [ colorTop, colorBottom]
gl.locations = [ 0.0, 1.0]
}
}

Why isn't this random color method working?

I have a for loop inside the drawRect method that draws a number of circles to fill the screen. I'm trying to make it so each circle has a new random stroke. For some reason nothing is showing up. Here is my randomColor method:
-(UIColor *) randomColor
{
int red, green, blue, alpha;
red = arc4random_uniform(255);
green = arc4random_uniform(255);
blue = arc4random_uniform(255);
alpha = arc4random_uniform(255);
UIColor *colorToReturn = [[UIColor alloc] initWithRed:red green:green blue:blue alpha:alpha];
return colorToReturn;
}
and I try implementing it here:
-(void) drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect bounds = [self bounds];
// Firgure out the center of the bounds rectangle
CGPoint center;
center.x = bounds.origin.x + bounds.size.width / 2.0;
center.y = bounds.origin.y + bounds.size.height / 2.0;
// The radius of the circle should be nearly as big as the view
float maxRadius = hypot(bounds.size.width, bounds.size.height) / 2.0;
// The thickness of the line should be 10 points wide
CGContextSetLineWidth(ctx, 10);
// The color of the line should be gray (red/green/blue = 0.6, alpha = 1.0)
// CGContextSetRGBStrokeColor(ctx, 0.6, 0.6, 0.6, 1.0);
// The same as
// [[UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1.0] setStroke];
// The same as
// [[UIColor redColor] setStroke];
// Draw concentric circles from the outside in
for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) {
// Add a path to the context
CGContextAddArc(ctx, center.x, center.y, currentRadius, 0.0, M_PI * 2.0, YES);
[[self randomColor] setStroke];
// Perform drawing instructions; removes path
CGContextStrokePath(ctx);
}
UIColor takes a float between 0 and 1 as a value for its RGB components:
UIColor *colorToReturn = [[UIColor alloc] initWithRed:red/255.0 green:green/255.0 blue:blue/255.0 alpha:alpha];
I use the two macros below to get a random color. The first one is a straightforward macro that I often use while setting colors. The second one returns a random color using it:
#define _RGB(r,g,b,a) [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:a]
#define kCLR_RANDOM_COLOR _RGB(arc4random()%255, arc4random()%255, arc4random()%255, 1)

Invert a radial gradient mask in core graphics

I am able to mask a picture with black/white radial gradient in iOS using core graphics. But I am getting result just the opposite I want. I want the picture to be transparent from the region where gradient is applied and shall display the remaining part of the picture. That means a "![hole][1]" in the picture. But here the hole will be created by a gradient mask.
Can anyone please suggest me some way or any hint to invert the radial gradient mask, just like we do in photoshop.
Instead of using mask, I think the best way is to insert the layer. That's what I did for my IOS app.
- (void) addSublayer {
CGPoint center = CGPointMake(self.addButton.frame.origin.x, self.addButton.frame.origin.y);
UIBezierPath* clip = [UIBezierPath bezierPathWithArcCenter:center
radius:CGRectGetMaxX(self.addUnavailabilitySubView.frame) - self.addButton.center.x
startAngle:-1.57
endAngle:1.57
clockwise:YES];
[clip addLineToPoint:center];
[clip closePath];
CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init];
[shapeLayer setMasksToBounds:YES];
shapeLayer.frame = self.view.layer.bounds;
shapeLayer.path = clip.CGPath;
//This gradient layer can be a customised one
CALayer *gradientLayer = [CALayer layer];
gradientLayer.frame = CGRectMake(0, 0, self.view.bounds.size.height, self.view.bounds.size.width);
[self.view.layer insertSublayer:gradientLayer atIndex:0];
}
I can also share my radial gradient layer here :)
-(void)drawInContext:(CGContextRef)ctx {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 }; // End color
CGFloat components[8] = {0.f, 0.f, 0.f, 0.3f, 0.f, 0.f, 0.f, 0.8f};
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, num_locations);
CGFloat myStartRadius, myEndRadius;
CGPoint radialCenter = CGPointMake(100, 100)
myStartRadius = 20;
myEndRadius = 300;
CGContextDrawRadialGradient (ctx, gradient, radialCenter,
myStartRadius, radialCenter, myEndRadius,
kCGGradientDrawsAfterEndLocation);
}

Applying gradient to UIImage - how to remove color invertion?

I'm applying gradient to UIImage, all i want it to be dark on the bottom and slowly turn to clear or light gray in the middle.
It is mostly ok, but i have a problem that my image colors invert in some places under this gradient. This looks very annoying. How do i resolve that?
Here is my method for your convenience.
I have tried picking different blending modes, but it doesn't help. Another question, is how do I make black more intensive in the bottom?
This method will work without any extra code.
Start color is [UIColor blackColor] and end color is [UIColor clearColor] :
- (UIImage *)imageWithGradient:(UIImage *)img startColor:(UIColor *)color1 endColor:(UIColor *)color2 {
UIGraphicsBeginImageContextWithOptions(img.size, NO, img.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, img.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetBlendMode(context, kCGBlendModeDarken); // tried normal here, same results
CGRect rect = CGRectMake(0, 0, img.size.width, img.size.height);
CGContextDrawImage(context, rect, img.CGImage);
// Create gradient
NSArray *colors = [NSArray arrayWithObjects:(id)color1.CGColor, (id)color2.CGColor, nil];
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColors(space, (__bridge CFArrayRef)colors, NULL);
// Apply gradient
CGContextClipToMask(context, rect, img.CGImage);
CGContextDrawLinearGradient(context, gradient, CGPointMake(0,0), CGPointMake(0, img.size.height / 2.0), 0);
UIImage *gradientImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGGradientRelease(gradient);
CGColorSpaceRelease(space);
return gradientImage;
}
Try this
// Set image over layer
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = imageView.frame;
// Add colors to layer
UIColor *centerColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.25];
UIColor *endColor = [UIColor grayColor];
gradient.colors = [NSArray arrayWithObjects:
(id)[endColor CGColor],
(id)[centerColor CGColor],
(id)[endColor CGColor],
nil];
[imageView.layer insertSublayer:gradient atIndex:0];
Just change array of gradient.colors depend on your requirements.
Navnath answer in Swift
var gradient = CAGradientLayer()
gradient.frame = imageView.frame
gradient.colors = [
UIColor(white: 0, alpha: 0.25).CGColor,
UIColor.grayColor().CGColor,
UIColor(white: 0, alpha: 0.25).CGColor
]
imageView.layer.insertSublayer(gradient, atIndex: 0)
Hope you find it useful
Slight correction to Navnath's and Francis's answers...
gradient.frame needs to be set to imageView.bounds:
gradient.frame = imageView.bounds
Try just changing the alpha to get the desired result. Place the layer over top of your existing image.

shadow does not show in drawRect:(CGrect)rect,objective

My goal is
1. add gradient for my view (done)
2. add drop shadow for the bottom edge of my view ( issue at here )
What I am doing is :
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
UIColor *whiteColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
UIColor *lightGrayColor = [UIColor colorWithRed:230.0/255.0 green:230.0/255.0 blue:230.0/255.0 alpha:1.0];
CGRect paperRect = self.bounds;
// Fill with gradient
[self drawLinearGradient:context for:paperRect start:whiteColor.CGColor end:lightGrayColor.CGColor];
CGContextSetStrokeColorWithColor(context, [UIColor lightGrayColor].CGColor);
CGContextSetLineWidth(context, .9);
CGContextStrokeRect(context, paperRect);
// Add shadow
CGContextSetShadowWithColor(context, CGSizeMake(0, self.frame.size.height), 9.0, [UIColor blueColor].CGColor);
}
-(void)drawLinearGradient:(CGContextRef)context for:(CGRect)rect start:(CGColorRef)startColor end:(CGColorRef)endColor {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat locations[] = { 0.0, 1.0 };
NSArray *colors = [NSArray arrayWithObjects:(__bridge id)startColor, (__bridge id)endColor, nil];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) colors, locations);
CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
CGContextSaveGState(context);
CGContextAddRect(context, rect);
CGContextClip(context);
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGContextRestoreGState(context);
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
}
However, what I am getting is the picture below without shadow at all
Am I on the right track but missing something in the middle ? Please help if you have any ideas..
You need to set the shadow properties before drawing whatever it is that's supposed to have the shadow. Put your call to CGContextSetShadowWithColor before the call to CGContextStrokeRect.
Also, the offset should probably not be as large as you're making it. The shadow offset controls how much the shadow is offset from the pixels that were drawn with that shadow, so unless you want the shadow to actually start very far away from your rect you probably want an offset of just a pixel or so, like CGSizeMake(0.0, 1.0).

Resources