How to set gradient color with angle - ios

I want to set gradient on my button, i have to set simple linear gradient(without angle) with two color, but i don't know how to set angle value on gradient
Angle:- 61
below image define psd gradient overlay effect
Thanks in advance

Try it, maybe it will help
- (CAGradientLayer *)gradientLayerWithColors:(NSArray *)colors angle:(CGFloat)angle {
CAGradientLayer *layer = [CAGradientLayer layer];
layer.colors = colors;
CGFloat x = angle / 360.f;
CGFloat a = pow(sin((2*M_PI*((x+0.75)/2))),2);
CGFloat b = pow(sin((2*M_PI*((x+0.0)/2))),2);
CGFloat c = pow(sin((2*M_PI*((x+0.25)/2))),2);
CGFloat d = pow(sin((2*M_PI*((x+0.5)/2))),2);
layer.startPoint = CGPointMake(a, b);
layer.endPoint = CGPointMake(c, d);
return layer;
}

CAGradientLayer *gradientMask = [CAGradientLayer layer];
gradientMask.frame = CGRectMake(0, 0, myView.bounds.size.width, myView.bounds.size.height);
gradientMask.colors = #[(id)[UIColor redColor].CGColor,
(id)[UIColor greenColor].CGColor];
gradientMask.locations = #[#0.0, #0.10, #0.60, #1.0];
[myView.layer insertSublayer:gradientMask atIndex:0];
adjust location value and frame size accordingly.

This is a slightly modified code generated by PaintCode that solves just this problem:
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRect: self.view.bounds];
UIBezierPath* rectangleRotatedPath = [rectanglePath copy];
CGAffineTransform transform = CGAffineTransformMakeRotation(yourAngleInRadians);
[rectangleRotatedPath applyTransform: transform];
CGRect rectangleBounds = CGPathGetPathBoundingBox(rectangleRotatedPath.CGPath);
transform = CGAffineTransformInvert(transform);
CGPoint startPoint = CGPointApplyAffineTransform(CGPointMake(CGRectGetMinX(rectangleBounds), CGRectGetMidY(rectangleBounds)), transform);
CGPoint endPoint = CGPointApplyAffineTransform(CGPointMake(CGRectGetMaxX(rectangleBounds), CGRectGetMidY(rectangleBounds)), transform);
The code takes your initial rectangle (black rectangle) and angle (shown as black line), rotates the rectangle (you'll get blue rectangle), then finds bounding box of the rotated rectangle (red rectangle). Takes endpoints on the bounds (red dots) and transforms (rotates) them back using invert transform (blue dots).
Other solution might be finding intersection of your rectangle and a line defined by the angle.

Related

Objective c : strange behaviour when masking layer with another layer and changing frame

UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:CGPointZero radius:80 startAngle:0 endAngle:(2 * M_PI) clockwise:YES];
CAShapeLayer * layer = [CAShapeLayer new];
layer.strokeColor = [UIColor redColor].CGColor;
layer.fillColor = [UIColor clearColor].CGColor;
layer.lineWidth = 10;
layer.strokeEnd = 1;
layer.position = self.view.center;
layer.lineCap = kCALineCapRound;
[layer setPath : path.CGPath];
[self.view.layer addSublayer : layer];
CAGradientLayer * gradientLayer = [CAGradientLayer layer];
gradientLayer.startPoint = CGPointMake(0.0,0.5);
gradientLayer.endPoint = CGPointMake(1.0,0.5);
gradientLayer.frame = self.view.frame;
NSArray *colors = #[(id)[UIColor greenColor].CGColor,(id)[UIColor blueColor].CGColor,(id)[UIColor yellowColor].CGColor];
gradientLayer.colors = colors;
[gradientLayer setMask : layer];
[self.view.layer addSublayer : gradientLayer];
What Im doing:
Adding CAShapeLayer to view.layer with a path to draw a circle.
Adding CAGradientLayer ( 3 colors ) to view.layer and setingMask to the CAShapeLayer.
Result: with and without mask
The problem :
If I change the frame of the gradient to be on top of the circle ( because I want to see the all the colors on the circle ) its moves the circle as well.
particularly the change to the x and y of the gradient
So instead of
gradientLayer.frame = self.view.frame;
I change the x and y only ( width and hight doesn't causing this problem as far as I checked )
gradientLayer.frame = CGRectMake(100,100, self.view.frame.size.width, self.view.frame.size.height);
And this is the result
Can anyone explain why this is happening and what are the possible solutions ?
What Im trying to achieve is this
And then masking it with the CAShapeLayer but then this problem happens
Thanks in advance
change the shapelayer position, because gradient layer is fullscreen and shapelayer set center position and after you change the frame of gradient layer shapelayer also change with same position.
layer.position = CGPointMake(30, 250);

How to create a gradient perimeter using CALayer

I've been trying for some time now, using CAGradientLayer to produce this .
Initially I tried having a gradient background using the .colors property, however this is only a background fill. Trying this approach, I tried to add another CALayer inside that had a black background, however i could never get the radius correct, and it would create a line of various thickness at the rounded corners.
Is there a better way to create this rounded rect border with a gradient to it? Will CALayer not achieve this and should I move over to UIBezierPath or CGContextRef?
Code for failed attempt:
UIView *view = [[UIView alloc]initWithFrame:CGRectMake(12*twelthWidth - squareCentre.x - squareSize.width, squareCentre.y, squareSize.width, squareSize.height)];
// Create the rounded layer, and mask it using the rounded mask layer
CAGradientLayer *roundedLayer = [CAGradientLayer layer];
[roundedLayer setFrame:view.bounds];
roundedLayer.cornerRadius = view.bounds.size.width/5;
roundedLayer.masksToBounds = YES;
roundedLayer.colors = [NSArray arrayWithObjects:(id)[[UIColor redColor] CGColor], (id)[[UIColor blueColor] CGColor], nil];
roundedLayer.borderWidth = 4;
roundedLayer.borderColor = [UIColor clearColor].CGColor;
// Add these two layers as sublayers to the view
int BorderWidth = 5;
CALayer *solidColour = [CALayer layer];
solidColour.cornerRadius = view.bounds.size.width/5;
solidColour.masksToBounds = YES;
solidColour.backgroundColor = [UIColor blackColor].CGColor;
[solidColour setFrame:CGRectMake(BorderWidth, BorderWidth, roundedLayer.bounds.size.width - 2*BorderWidth, roundedLayer.bounds.size.height - 2*BorderWidth)];
[view.layer insertSublayer:roundedLayer atIndex:0];
[view.layer insertSublayer:solidColour above:roundedLayer];
[self.view addSubview:view];
Which produces:
Whereby the corners aren't right. Could it be that I need to calculate a different radius for the second layer?
Edit
After setting the radius of the solid colour to solidColour.bounds.size.width/5, it still isn't right - It goes too thin at the corners.
The problem you are seeing is because the inner and outer corner radius are the same. That is what causes the line thickness to vary. This illustration from CSS-Tricks highlights the issue (even thought you aren't using CSS, the problem is still the same):
The solution is to calculate the inner radius as:
innerRadis = outerRadius - lineThickness
As shown in this illustration by Joshua Hibbert:
Instead of using the view bounds to set the cornerRadius of the solid layer, use the layer's frame value after you've set it to the correct inset value:
solidColour.masksToBounds = YES;
solidColour.backgroundColor = [UIColor blackColor].CGColor;
[solidColour setFrame:CGRectMake(BorderWidth, BorderWidth, roundedLayer.bounds.size.width - 2*BorderWidth, roundedLayer.bounds.size.height - 2*BorderWidth)];
solidColour.cornerRadius = solidColour.frame.size.width/5;
[view.layer insertSublayer:roundedLayer atIndex:0];
[view.layer insertSublayer:solidColour above:roundedLayer];
With this I get the following image
Just for your convenience if you use swift
let v = yourUIVIEW
let outerRadius:CGFloat = 60
let borderWidth:CGFloat = 8;
let roundedLayer = CAGradientLayer()
roundedLayer.frame = v.bounds
roundedLayer.cornerRadius = outerRadius
roundedLayer.masksToBounds = true
roundedLayer.endPoint = CGPoint(x: 1 , y: 0.5)
roundedLayer.colors = [ UIColor.redColor().CGColor, UIColor.blueColor()!.CGColor]
let innerRadius = outerRadius - borderWidth
//Solid layer
let solidLayer = CALayer()
solidLayer.masksToBounds = true
solidLayer.backgroundColor = UIColor.whiteColor().CGColor
solidLayer.cornerRadius = innerRadius
solidLayer.frame = CGRect(x: borderWidth, y: borderWidth, width: roundedLayer.bounds.width - 2*borderWidth, height:roundedLayer.bounds.height - 2*borderWidth )
v.layer.insertSublayer(roundedLayer, atIndex: 0)
v.layer.insertSublayer(solidLayer, above: roundedLayer)

CGRect Intersects Rect better alternative

I'm trying to want to write an method whenever two UIImageViews intersect. The only way I know how to do it is CGRectIntersectsRect. But that only works with rectangles, but my images are circles. Isn't there a better alternative? Thank you!
You can do something like this:
CGPoint a = imageViewA.center;
CGPoint b = imageViewB.center;
CGFloat distance = sqrt(pow((b.x - a.x),2) + pow((b.y - a.y),2));
if(distance < (imageViewA.bounds.size.width/2.0 + imageViewB.bounds.size.width/2.0)){
//images are touching.
}
Assuming you know the radius of each circle (if the image is a square in size then it will be height/2 or width/2 if the circle entirely occupies fills the image) do the following for detecting collision between two circles:
Calculate the distance between the center point of the two circle:
distance = sqrt((p1.x - p2.x)^2 + (p1.y - p2.y)^2);
p1 - Center point of circle 1
p2 - Center point of circle 2
Calculate the sum of the radius of the two circles :
radiusSum = radius1 + radius2;
If distance <= radiusSum then you have a collision
It's use core animation.
CALayer *subLayer = [CALayer layer];
subLayer.backgroundColor = [UIColor blueColor].CGColor;
subLayer.shadowOffset = CGSizeMake(0, 3);
subLayer.shadowRadius = 100.0;
subLayer.shadowColor = [UIColor blackColor].CGColor;
subLayer.shadowOpacity = 0.8;
subLayer.frame = CGRectMake(30, 30, 128, 192);
subLayer.borderColor = [UIColor blackColor].CGColor;
subLayer.borderWidth = 2.0;
subLayer.cornerRadius = 10.0;
[self.view.layer addSublayer:subLayer];
CALayer *imageLayer = [CALayer layer];
imageLayer.frame = subLayer.bounds;
imageLayer.cornerRadius = 48.0;// Here set you right size, then they looks like a circles
imageLayer.contents = (id)[UIImage imageNamed:#"141.png"].CGImage;
imageLayer.masksToBounds = YES;
[subLayer addSublayer:imageLayer];

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

iOS Direction Glow

I'm trying to create a "mini-map." It's a circle filled with markers, and I want the border of the circle to glow in places to indicate to the user to indicate that more markers are beyond the minimap in a given direction.
I can give the circle a 'glowing' blue border by drawing, below it, a blue circle with a slightly larger radius. I think that I can make this blue border brighter in some places than others by giving its CALayer a mask. (I've tried giving it a gradient mask, and it works.)
Assuming that I can make the proper calculations to determine how bright a given pixel should be (given the position of markers beyond the minimap's viewport), how do I set the individual pixels of a CALayer? Or is there an easier way to accomplish what I'm looking for besides making a complicated alpha value calculation for each pixel in the circle?
Thanks.
Here's my solution. I drew a series of 1-pixel arcs, each with a different stroke color.
void AddGlowArc(CGContextRef context, CGFloat x, CGFloat y, CGFloat radius, CGFloat peakAngle, CGFloat sideAngle, CGColorRef colorRef){
CGFloat increment = .05;
for (CGFloat angle = peakAngle - sideAngle; angle < peakAngle + sideAngle; angle+=increment){
CGFloat alpha = (sideAngle - fabs(angle - peakAngle)) / sideAngle;
CGColorRef newColor = CGColorCreateCopyWithAlpha(colorRef, alpha);
CGContextSetStrokeColorWithColor(context, newColor);
CGContextAddArc(context, x, y, radius, angle, angle + increment, 0);
CGContextStrokePath(context);
}
}
And then, in DrawRect,
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2.0);
AddGlowArc(context, 160, 160, 160, angle, .2, [UIColor colorWithRed:0 green:.76 blue:.87 alpha:1].CGColor);
This was my [much longer] solution which would allow each glow-point layer to be added and animated individually if needed. The small circle is added on the circumference of a larger one and that larger circle is rotated. Enjoyed getting my head around this even though you answered the question
- (void)viewDidLoad
{
[super viewDidLoad];
CGPoint circleCenter = CGPointMake(300.f, 300.f);
CALayer *outerCircle = [self outerCircleToRotateWithCenter:circleCenter andRadius:100.f];
[self.view.layer addSublayer:outerCircle];
CGFloat rotationAngleInDeg = 270.f;
CGFloat rotationAngle = (M_PI * -rotationAngleInDeg)/180.f;
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DRotate(transform, rotationAngle, 0.f, 0.f, -1.f);
[outerCircle setTransform:transform];
}
Using method:
- (CALayer *)outerCircleToRotateWithCenter:(CGPoint)circleCenter andRadius:(CGFloat )radius {
// outer circle
CAShapeLayer *container = [CAShapeLayer layer];
UIBezierPath *containerCircle = [UIBezierPath
bezierPathWithOvalInRect:CGRectMake(0.f, 0.f, radius, radius)];
[container setPath:containerCircle.CGPath];
[container setBounds:CGPathGetBoundingBox(containerCircle.CGPath)];
[container setPosition:circleCenter];
[container setAnchorPoint:CGPointMake(0.5f, 0.5f)];
[container setStrokeColor:[UIColor blackColor].CGColor]; // REMOVE
[container setFillColor:nil];
// smaller circle
CAShapeLayer *circle = [[CAShapeLayer alloc] init];
[container addSublayer:circle];
UIBezierPath *circlePath = [UIBezierPath
bezierPathWithOvalInRect:CGRectMake(0.f, 0.f, 25.f, 25.f)];
[circle setPath:circlePath.CGPath];
[circle setBounds:CGPathGetBoundingBox(circlePath.CGPath)];
[circle setFillColor:[UIColor orangeColor].CGColor];
[circle setAnchorPoint:CGPointMake(0.5f, 0.5f)];
[circle setPosition:CGPointMake(radius/2.f, 0.f)];
[circle setOpacity:0.4f];
// shadow
[circle setShadowOpacity:0.8f];
[circle setShadowRadius:4.f];
[circle setShadowOffset:CGSizeMake(0.f, 0.f)];
[circle setShadowColor:[UIColor orangeColor].CGColor];
[circle setShadowPath:circlePath.CGPath];
return container;
}
I'm guessing the smaller circle could be added and rotated in a single transform rather then being a sublayer.

Resources