CGRect Intersects Rect better alternative - ios

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];

Related

How to set gradient color with angle

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.

How to animate the fill of a CAShapeLayer?

I have a circular CAShapeLayer that I'd like to animate the fill of. I have created my CAShapeLayer as
- (void)viewDidLoad {
[super viewDidLoad];
// Filled layer
CAShapeLayer *filledCircleShape = [CAShapeLayer new];
filledCircleShape.frame = CGRectMake(100, 100, 100, 100);
filledCircleShape.fillColor = [UIColor purpleColor].CGColor;
filledCircleShape.strokeColor = [UIColor grayColor].CGColor;
filledCircleShape.lineWidth = 2;
filledCircleShape.path = [self filledBezierPathWithRelativeFill:.75 inFrame:filledCircleShape.frame].CGPath;
[self.view.layer addSublayer:filledCircleShape];
self.filleShapeLayer = filledCircleShape;
}
- (UIBezierPath *)filledBezierPathWithRelativeFill:(CGFloat)relativeFill
{
UIBezierPath *filledCircleArc = [UIBezierPath bezierPath];
CGFloat arcAngle = 2 * acos(1 - 2 * relativeFill); // 2arccos ((r - 2rp) / r) == 2 arccos(1 - 2p)
CGFloat startAngle = (M_PI - arcAngle) / 2;
CGFloat endAngle = startAngle + arcAngle;
[filledCircleArc addArcWithCenter:self.boundsCenter radius:self.radius startAngle:startAngle endAngle:endAngle clockwise:YES];
return filledCircleArc;
}
This looks like this:
I have tried the following to make it animate a path change.
- (void)animate
{
UIBezierPath *toBezierPath = [self filledBezierPathWithRelativeFill:0.3 inFrame:CGRectMake(100, 100, 100, 100)];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"path"];
pathAnimation.duration = 5.f;
pathAnimation.toValue = (__bridge id)toBezierPath.CGPath;
[self.filleShapeLayer addAnimation:pathAnimation forKey:#"path"];
}
which kind of works. The shape layer animates but looks like this
I want the shape layer to animate like an hour glass, starting at one percentage and then animates down to another.
Any ideas of how I can achieve this using a CAShapeLayer?
The CAShapeLayer is overkill for the fill. It isn't magically going to animate the way you want, and it isn't necessary anyway. Just start with a big square purple view and animate it downwards — but put a mask so that only the interior of the shape region is visible — as I do here (except that I'm filling instead of emptying):

start and end angle of UIBezierPath?

I have coded as below for half circle in iOS using UIBezierPath and CAShapeLayer.
clockWiseLayer = [[CAShapeLayer alloc] init];
CGFloat startAngle = -M_PI_2;
CGFloat endAngle = M_PI + M_PI_2;
CGFloat width = CGRectGetWidth(imageView.frame)/2.0f + 30;
CGFloat height = CGRectGetHeight(imageView.frame)/2.0f +50;
CGPoint centerPoint = CGPointMake(width, height);
float radius = CGRectGetWidth(imageView.frame)/2+10;
clockWiseLayer.path = [UIBezierPath bezierPathWithArcCenter:centerPoint
radius:radius
startAngle:startAngle
endAngle:endAngle
clockwise:YES].CGPath;
clockWiseLayer.fillColor = [UIColor clearColor].CGColor;
clockWiseLayer.strokeColor = [UIColor blueColor].CGColor;
clockWiseLayer.borderColor = [UIColor greenColor].CGColor;
clockWiseLayer.backgroundColor = [UIColor redColor].CGColor;
clockWiseLayer.strokeStart = 0.0f;
clockWiseLayer.strokeEnd = 0.5f;
clockWiseLayer.lineWidth = 2.0f;
clockWiseLayer.borderWidth = 5.0f;
clockWiseLayer.shouldRasterize = NO;
[self.layer addSublayer:clockWiseLayer];
This results as below.
I want this blue half circle on the opposite side of the World Globe.
It is half Circle, but I want it on the other side, also CounterClockWise.
I am unable to set start and end angle for that, while clockwise:NO.
Thanks.
Check documentation for coordinates:
http://i.stack.imgur.com/1yJo6.png
Y-Axis is reversed compared to standard coordinate system in mathematics.
You should draw from 1/2*PI (bottom anchor) to 3/2*PI (top anchor) and then you set strokeStart to 0.0f and strokeEnd to 1.0f (so it fills whole path).
Working code using iOS constants:
CGFloat startAngle = M_PI_2;
CGFloat endAngle = startAngle + M_PI;
CGFloat width = CGRectGetWidth(imageView.frame)/2.0f;
CGFloat height = CGRectGetHeight(imageView.frame)/2.0f;
CGPoint centerPoint = CGPointMake(width, height);
float radius = CGRectGetWidth(imageView.frame)/2+10;
CAShapeLayer* clockWiseLayer = [[CAShapeLayer alloc] init];
clockWiseLayer.path = [UIBezierPath bezierPathWithArcCenter:centerPoint
radius:radius
startAngle:startAngle
endAngle:endAngle
clockwise:YES].CGPath;
clockWiseLayer.fillColor = [UIColor clearColor].CGColor;
clockWiseLayer.strokeColor = [UIColor blueColor].CGColor;
clockWiseLayer.borderColor = [UIColor greenColor].CGColor;
clockWiseLayer.backgroundColor = [UIColor redColor].CGColor;
clockWiseLayer.strokeStart = 0.0f;
clockWiseLayer.strokeEnd = 1.0f;
clockWiseLayer.lineWidth = 2.0f;
clockWiseLayer.borderWidth = 5.0f;
[self.layer addSublayer:clockWiseLayer];
You start from top of the circle with -M_PI_2 and end at M_PI + M_PI_2 (if you want to have full circle and limit it using strokeEnd, strokeStart). Then set the circle path to draw from half of the end of the path (left side of image) instead from beginning to half (right side of the image)
CGFloat startAngle = -M_PI_2;
CGFloat endAngle = M_PI + M_PI_2;
clockWiseLayer.strokeStart = .5f;
clockWiseLayer.strokeEnd = 1.0f;
I have played with bezierPathWithArcCenter and it works quite strange if clockwise = NO. But if you want to create an circle bezier path with conterclockwise direction you can create a clockwise path and then revert it with bezierPathByReversingPath
A new path object with the same path shape but for which the path has been created in the reverse direction.

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)

Getting a wave like animation in concentric circles in ios

I have this need to display two points emitting signals. The way the signals is represented is the wifi signal strength indicator that we commonly see in Macs.
However, there are a couple of changes to that:
It is inverted. The source is pointed at top. Look at the image below:
And I need to animate the lines to give the indication that the source is emitting signal.
I was able to get the attached images by overriding the drawRect of the View class:
CGContext context = UIGraphics.GetCurrentContext();
context.SetLineWidth(2.0f);
UIColor color = UIColor.FromRGB(65, 137, 77);
context.SetStrokeColorWithColor(color.CGColor);
float maxRadius = rect.Height;
const float delta = 15;
float Y = center.Y;
for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= delta) {
context.AddArc(center.X, Y, currentRadius, -ToRadians(startAngle), -ToRadians(endAngle), true);
context.StrokePath();
}
I'm out of my depths here.
If someone can point me to the right direction, that would be super awesome!
I took a shot at this using Core Animation. I just add a shape layer with a circle shape for each of the waves and then dynamically adjust their strokeStart and strokeEnd properties to get it to vary the width of each wave. So the output is like this:
Here is the initialization code:
- (void)addWavesToView
{
CGRect rect = CGRectMake(0.0f, 0.0f, 300.0f, 300.0f);
UIBezierPath *circlePath = [UIBezierPath bezierPathWithOvalInRect:rect];
for (NSInteger i = 0; i < 10; ++i) {
CAShapeLayer *waveLayer = [CAShapeLayer layer];
waveLayer.bounds = rect;
waveLayer.position = CGPointMake(300.0f, 100.0f);
waveLayer.strokeColor = [[UIColor lightGrayColor] CGColor];
waveLayer.fillColor = [[UIColor clearColor] CGColor];
waveLayer.lineWidth = 5.0f;
waveLayer.path = circlePath.CGPath;
// Translate on the y axis to shift all the layers down while
// we're creating them. Could do this with the position value as well
// but this is a little cleaner.
waveLayer.transform = CATransform3DMakeTranslation(0.0f, i*25, 0.0f);
// strokeStart begins at 3:00. You would need to transform (rotate)
// the layer 90 deg CCW to have it start at 12:00
waveLayer.strokeStart = 0.25 - ((i+1) * 0.01f);
waveLayer.strokeEnd = 0.25 + ((i+1) * 0.01f);
[self.view.layer addSublayer:waveLayer];
}
}
And then here is where I add the animations for each layer:
- (void)addAnimationsToLayers
{
NSInteger timeOffset = 10;
for (CALayer *layer in self.view.layer.sublayers) {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"strokeColor"];
animation.duration = 10.0f;
// Stagger the animations so they don't begin until the
// desired time in each
animation.timeOffset = timeOffset--;
animation.toValue = (id)[[UIColor lightGrayColor] CGColor];
animation.fromValue = (id)[[UIColor darkGrayColor] CGColor];
// Repeat forever
animation.repeatCount = HUGE_VALF;
// Run it at 10x. You can adjust this to taste.
animation.speed = 10;
// Add to the layer to start the animation
[layer addAnimation:animation forKey:#"strokeColor"];
}
}
I posted it to github. Not sure if it's exactly what you're looking for, but hopefully it points you in the right direction.

Resources