iOS: How to animate an image along a sine curve? - ios

As in the title stated, I want to to animate some bubbles that travel along a sine curve from the bottom to the top within an UIView using CG/CA or even OpenGL if necessary.
Here is my CA code snippet that works fine, but it's a straight line which is animated. How can I build in the sine curve behaviour ?
- (void)animateBubble:(UIImageView *)imgView {
[UIView beginAnimations:[NSString stringWithFormat:#"%i",imgView.tag] context:nil];
[UIView setAnimationDuration:6];
[UIView setAnimationDelegate:self];
imgView.frame = CGRectMake(imgView.frame.origin.x, 0, imgView.frame.size.width, imgView.frame.size.height);
[UIView commitAnimations];
}
I've already achieved the wanted result with SpriteKit (have a look: http://youtu.be/Gnj3UAD3gQI). The bubbles travel along a sine curve from bottom to the top where the amplitude is within a random range. Moreover, I use the gyroscope sensors to additionally influence the path.
Is this behaviour somehow reproducible with UIKit, CG, CA (if absolutely necessary also with OpenGL) ? Code samples would be wonderful, but any other ideas are also highly appreciated.

To animate along a path, you first need to define that path. If you really need a true sine curve, we can show you how to do that, but it's probably easiest to define something that approximates a sine curve using two quadratic bezier curves:
CGFloat width = ...
CGFloat height = ...
CGPoint startPoint = ...
CGPoint point = startPoint;
CGPoint controlPoint = CGPointMake(point.x + width / 4.0, point.y - height / 4.0);
CGPoint nextPoint = CGPointMake(point.x + width / 2.0, point.y);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:point];
[path addQuadCurveToPoint:nextPoint controlPoint:controlPoint];
point = nextPoint;
controlPoint = CGPointMake(point.x + width / 4.0, point.y + height / 4.0);
nextPoint = CGPointMake(point.x + width / 2.0, point.y);
[path addQuadCurveToPoint:nextPoint controlPoint:controlPoint];
That renders path like so:
Obviously, change startPoint, width, and height to be whatever you want. Or repeat that process of adding more bezier paths if you need more iterations.
Anyway, having defined a path, rather than rendering the path itself, you can create a CAKeyframeAnimation that animates the position of the UIView along that path:
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
animation.duration = 1.0;
animation.path = path.CGPath;
[view.layer addAnimation:animation forKey:#"myPathAnimation"];

Related

How To Get A Perfect Position Of Rotating View?

I am have one view and I am rotating that view using CABasicAnimation.
Now my problem is that how I get a perfect position of that view while rotating. I have tried many type of codes but i can't got a perfect position during rotation of that view.
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
NSNumber *currentAngle = [CircleView.layer.presentationLayer valueForKeyPath:#"transform.rotation"];
rotationAnimation.fromValue = currentAngle;
rotationAnimation.toValue = #(50*M_PI);
rotationAnimation.duration = 50.0f; // this might be too fast
rotationAnimation.repeatCount = HUGE_VALF; // HUGE_VALF is defined in math.h so import it
[CircleView.layer addAnimation:rotationAnimation forKey:#"rotationAnimationleft"];
I am using this code for rotating my view.
I have also attached a one photo of my view.
Thank you In Advance Please Help If You Know.
To get view's parameters during the animation you should use view.layer.presentationLayer
ADDED:
In order to get the coordinate of top left corner of the view, use the following code:
- (CGPoint)currentTopLeftPointOfTheView:(UIView *)view
{
CGRect rect = view.bounds;
rect.origin.x = view.center.x - 0.5 * CGRectGetWidth(rect);
rect.origin.y = view.center.y - 0.5 * CGRectGetHeight(rect);
CGPoint originalTopLeftCorner = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect));
CGPoint rectCenter = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
CGFloat radius = sqrt(pow(rectCenter.x - originalTopLeftCorner.x, 2.0) + pow(rectCenter.y - originalTopLeftCorner.y, 2.0));
CGFloat originalAngle = M_PI - acos((rectCenter.x - originalTopLeftCorner.x) / radius);
CATransform3D currentTransform = ((CALayer *)view.layer.presentationLayer).transform;
CGFloat rotation = atan2(currentTransform.m12, currentTransform.m11);
CGFloat resultAngle = originalAngle - rotation;
CGPoint currentTopLeftCorner = CGPointMake(round(rectCenter.x + cos(resultAngle) * radius), round(rectCenter.y - sin(resultAngle) * radius));
return currentTopLeftCorner;
}
Resulting CGPoint will be the coordinate of the top left corner of your (rotated) view relative to its superview.
Set the position of the layer that you what
[layer setPosition:endpoint];
Or you may also refer this CABasicAnimation rotate returns to original position

CAKeyframeAnimation stops for some angle values of CGPathAddArc

i am trying to create an animation,
in which a circle rotates around the center of the view.
I want the circle to appear under the location of the tap gesture, therefore i calculate distance and angle between the center of the view and the circle.
I then set these values inside the CGPathAddArc for the animation path.
This seem to work rather well, but once in a while, after having performed a whole rotation, the circle will stop for a short while, before restarting.
By playing around, i found out that this must be related to the start and stop angles of the CGPathAddArc function, as setting standard values like -M_PI to M_PI will not cause the issue.
My code is:
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.fillMode = kCAFillModeRemoved;
pathAnimation.removedOnCompletion = YES;
pathAnimation.duration = 4.0;
pathAnimation.repeatCount = HUGE_VALF;
CGMutablePathRef curvedPath = CGPathCreateMutable();
//get the angle and dinstance of location in relation to center of view (mathematics)
CGFloat angle = atan2f(location.y -self.view.center.y, location.x - self.view.center.x);
CGFloat distance = hypotf(location.x - self.view.center.x, location.y - self.view.center.y);
NSLog(#"angle: %f", angle);
CGPathAddArc(curvedPath, NULL, self.view.center.x, self.view.center.y, distance, angle, angle + 2 * M_PI, NO);
//Now we have the path, we tell the animation we want to use this path - then we release the path
pathAnimation.path = curvedPath;
CGPathRelease(curvedPath);
//Add the animation to the circleView - once you add the animation to the layer, the animation starts
[self.view addSubview:circle];
[circle.layer addAnimation:pathAnimation forKey:#"moveTheSquare"];
location holds the coordinates of the touch event, the circle object is created before this code snippet. Any help would be greatly appreciated.
Adding this would smooth the animation out evenly:
pathAnimation.calculationMode = kCAAnimationPaced;
Just tested it.
Nice calculation, by the way.
Hope this helps.

Core Animation rotation around point

I would like to make an IBOutlet rotate around a specific point in the parent view,
currently i only know how to rotate it around an anchor point, however, i want to use a point outside the object's layer.
The rotation angle is calculated relative to the device heading from the point.
- (void)viewDidLoad{
[super viewDidLoad];
locationManager=[[CLLocationManager alloc] init];
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.headingFilter = 1;
locationManager.delegate=self;
[locationManager startUpdatingHeading];
[locationManager startUpdatingLocation];
compassImage.layer.anchorPoint=CGPointZero;
}
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading{
// Convert Degree to Radian and move the needle
float oldRad = -manager.heading.trueHeading * M_PI / 180.0f;
float newRad = -newHeading.trueHeading * M_PI / 180.0f;
CABasicAnimation *theAnimation;
theAnimation=[CABasicAnimation animationWithKeyPath:#"transform.rotation"];
theAnimation.fromValue = [NSNumber numberWithFloat:oldRad];
theAnimation.toValue=[NSNumber numberWithFloat:newRad];
theAnimation.duration = 0.5f;
[compassImage.layer addAnimation:theAnimation forKey:#"animateMyRotation"];
compassImage.transform = CGAffineTransformMakeRotation(newRad);
NSLog(#"%f (%f) => %f (%f)", manager.heading.trueHeading, oldRad, newHeading.trueHeading, newRad);
}
How can i rotate the UIImageView around (x,y) by alpha?
For rotating around a specific point (internal or external) you can change the anchor point of the layer and then apply a regular rotation transform animation similar to what I wrote about in this blog post.
You just need to be aware that the anchor point also affects where the layer appears on the screen. When you change the anchor point you will also have to change the position to make the layer appear in the same place on the screen.
Assuming that the layer is already placed in it's start position and that the point to rotate around is known, the anchor point and the position can be calculated like this (note that the anchor point is in the unit coordinate space of the layer's bounds (meaning that x and y range from 0 to 1 within the bounds)):
CGPoint rotationPoint = // The point we are rotating around
CGFloat minX = CGRectGetMinX(view.frame);
CGFloat minY = CGRectGetMinY(view.frame);
CGFloat width = CGRectGetWidth(view.frame);
CGFloat height = CGRectGetHeight(view.frame);
CGPoint anchorPoint = CGPointMake((rotationPoint.x-minX)/width,
(rotationPoint.y-minY)/height);
view.layer.anchorPoint = anchorPoint;
view.layer.position = rotationPoint;
Then you simply apply a rotation animation to it, for example:
CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotate.toValue = #(-M_PI_2); // The angle we are rotating to
rotate.duration = 1.0;
[view.layer addAnimation:rotate forKey:#"myRotationAnimation"];
Just note that you have changed the anchor point so the position is no longer in the center of the frame and if you apply other transforms (such as a scale) they will also be relative to the anchor point.
Translated David's answer to swift 3:
let rotationPoint = CGPoint(x: layer.frame.width / 2.0, y: layer.frame.height / 2.0) // The point we are rotating around
print(rotationPoint.debugDescription)
let width = layer.frame.width
let height = layer.frame.height
let minX = layer.frame.minX
let minY = layer.frame.minY
let anchorPoint = CGPoint(x: (rotationPoint.x-minX)/width,
y: (rotationPoint.y-minY)/height)
layer.anchorPoint = anchorPoint;
layer.position = rotationPoint;

Label curve segments

I want to label every part of the following curve like in the figure. The label origin should start from the middle of the curve segment see the graphic. I think that something is not quite right in my logic. Could you assist where is the error in my code? I think that the code and the figure explain itself.
Thanks for your time and help!
CGFloat radius = self.height / 3.2f; // radius of the curve
CGPoint center = CGPointMake(self.width / 2.f, self.height / 2.f); //center of the curve
UIBezierPath* circlePath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:degreesToRadian(270.f) endAngle:DEGREES_TO_RADIANS(269.999f) clockwise:YES]; // go around like the clock
CGFloat strokeStart = 0.f;
// Add the parts of the curve to the view
for (int i = 0; i < segmentValues.count; i++) {
NSNumber *value = [segmentValues objectAtIndex:i];
CGFloat strokeEnd = [value floatValue] / kPercentHundred;
CAShapeLayer *circle = [CAShapeLayer layer];
circle.path = circlePath.CGPath;
circle.lineCap = kCALineCapButt;
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [[colors objectAtIndex:i] CGColor];
circle.lineWidth = kWidhtLine;
circle.zPosition = 1.f;
circle.strokeStart = strokeStart;
circle.strokeEnd = strokeStart + strokeEnd;
[self.layer addSublayer:circle];
UIView *example = [[UIView alloc] init];
example.backgroundColor = kColorRed;
example.size = CGSizeMake(10, 10);
// middle = "(circle.strokeStart + circle.strokeEnd) / 2.f"
// * convert percent to degree
CGFloat angle = (circle.strokeStart + circle.strokeEnd) / 2.f * 360.f;
// determine the point and add the right offset
example.origin = CGPointMake(cosf(angle) * radius + center.x, sinf(angle) * radius + center.y);
[self addSubview:example];
strokeStart = circle.strokeEnd;
}
What I get see the figure.
What I expect in concept is something similar to the following figure.
There are two key issues:
You calculate the angle via:
CGFloat angle = (circle.strokeStart + circle.strokeEnd) / 2.f * 360.f;
You then proceed to use that for the cos and sin functions. Those expect radian values, though, so you have to convert this to radians before you use it in those functions. Thus, yielding:
CGFloat angle = degreesToRadian((circle.strokeStart + circle.strokeEnd) / 2.f * 360.f);
(BTW, I don't know if you prefer degreesToRadian or DEGREES_TO_RADIANS, but you should probably use one or the other.)
Or, more simply, you can convert to radians directly:
CGFloat angle = (circle.strokeStart + circle.strokeEnd) / 2.f * M_PI * 2.0;
You have rotated the circle 90 degrees (presumably so that it would start a "12 o'clock" rather than "3 o'clock"). Well, if you rotate the circle, then you have to rotate angle when you calculate where to put your example view, as well:
example.center = CGPointMake(cosf(angle - M_PI_2) * radius + center.x, sinf(angle - M_PI_2) * radius + center.y);
This yields:
I'm not sure what your goal is. You say you want to create labels, but what you create in your code is generic UIView objects.
Then you are setting a property "origin" on the view, but UIView does not have an origin as far as I know. It has a frame, which has an origin component, but not an origin property. I'm guessing that the code you posted is not actual, running code.
Do you want the labels to be centered on top of the circle? If so, you should both set the text alignment to NSTextAlignmentCenter, and set the center property of the views rather than the frame.origin.
I have a working sample project on github that creates an analog clock and puts labels for the hours around it. You can look at the code for an idea of how to do it:
Analog clock sample project
(The project also shows how to use the new spring physics UIView animation method.)
hmm it i a little late for me ;)
I think you have to use UIView::setCenter this will solve you problem. Then the center of the view is on your circle.
Edit: And it is easier to manipulate the position.

Rotating a view around a fixed point - ObjectiveC

I need to rotate a UIView around a fixed point.
bubbleTail = [[BubbleTail alloc] initWithFrame:bubbleTailFrame];
[self addSubview:bubbleTail];
bubbleTail.layer.anchorPoint = triangle_top_left;
CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI * 180 / 180.0);
bubbleTail.transform = transform;
This works only if I comment the .anchorPoint line.
How do I rotate the view about a point inside the view?
The below should help. The tricky part is that setting the anchorpoint after adding a view with a frame then moves the view. I've gotten around this by initiating the UIView without a frame, then setting the position, bounds and anchorpoint of its layer. I then use the rotation to rotate around the anchor point.
CABasicAnimation *rotation;
rotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rotation.fromValue = [NSNumber numberWithFloat:0];
rotation.toValue = [NSNumber numberWithFloat:((360*M_PI)/180)];
rotation.duration = 60.0;
rotation.repeatCount = HUGE_VALF;
UIView *myview = [[UIView alloc] init];
[self.view addSubview:myview];
[myview setBackgroundColor:[UIColor orangeColor]];
[myview.layer setPosition:CGPointMake(10, 10)];
[myview.layer setBounds:CGRectMake(0, 0, 100, 100)];
[myview.layer setAnchorPoint:CGPointMake(0, 0)];
[myview.layer addAnimation:rotation forKey:#"myAnimation"];
Someone please correct me if this is not the best method or if there are mistakes - I only know what I currently know - e.g. when I should use a . or f after a float.
So it seems CGAffineTransformMakeRotation rotates it around the center of the view?
Say you have point P (px,py) in view, you want view to be rotated around that.
Let's A(ax,ay) = P - center = (px - centerx, py - centery).
And you have angle alpha.
Then you could make a translate matrix with - A, then multipy it with rotation matrix with alpha, then multipy it with translate matrix with + A.
The functions you'll need are: CGAffineTransformMakeTranslation, CGAffineTransformMakeRotation, CGAffineTransformConcat.
P.S. Not sure if this will work (also not sure how view rect is represented, maybe it first assumption about rotation around center is wrong, then you shoud assume A = P), but that's how it's done with affine transforms.

Resources