CAKeyframeAnimation stops for some angle values of CGPathAddArc - ios

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.

Related

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

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

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;

IOS current keyframe y axis

Hey im trying to show the y axis of my imageview while its moving , but its not displaying the animating y axis , its showing only one number .
//LINE ANAMATION
CALayer *layer = line.layer;
CGPoint startPoint = (CGPoint){line.center.x,20};
CGPoint endPoint = (CGPoint){line.center.x, screenSizeY/2};
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath, NULL, startPoint.x, startPoint.y);
CGPathAddLineToPoint(thePath, NULL, endPoint.x, endPoint.y);
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
animation.duration = 3.f;
animation.path = thePath;
animation.autoreverses = YES;
animation.repeatCount = INFINITY;
[layer addAnimation:animation forKey:#"position"];
NSLog(#"%f", line.center.y);
There are a couple of things wrong with this. First, the animation state is maintained in the presentation layer. Your view and underlying layer are will reflect the final state of the animation immediately. Second, your NSLog statement will only be called once because you aren't in a loop.
If you want to do something at each frame of an animation, you can use CADisplayLink to get a callback on each frame. In this callback, you can check the current Y position by looking at the presentation layer:
- (void)myCallback:(CADisplayLink *)link
{
NSLog(#"%f", CGRectGetMidY(line.layer.presentationLayer.frame));
}

Group animation on multiple objects

I'm trying to animate many bubbles coming out of a machine. I'm using a basic animation to scale the bubbles from a small bubble to a large one and a keyframe animation to send the bubble in a curve across the screen. These are combined into a group animation.
I have read through just about all I can to try to get this to work, but I have the following two problems.
I can't get the scale animation to work together with the keyframe
animation when I try to animate each bubble.
The animation happens to them all at the same time. I'd like to see them animated one
after the other, but they all animate together.
here is the animation code I have.
-(void)EmitBubbles:(UIButton*)bubblename :(CGRect)RectPassed
{
bubblename.hidden = NO;
// Set position and size of each bubble to be at head of bubble machine and size (0,0)
CGPoint point = (RectPassed.origin);
CGRect ButtonFrame;
ButtonFrame = bubblename.bounds;
ButtonFrame.size = CGSizeMake(0, 0);
bubblename.bounds = ButtonFrame;
CGRect OldButtonBounds = bubblename.bounds;
CGRect NewButtonBounds = OldButtonBounds;
NewButtonBounds.size = RectPassed.size;
CGFloat animationDuration = 0.8f;
// Set up scaling
CABasicAnimation *resizeAnimation = [CABasicAnimation animationWithKeyPath:#"bounds.size"];
resizeAnimation.fillMode = kCAFillModeForwards;
resizeAnimation.duration = animationDuration;
resizeAnimation.fromValue = [NSValue valueWithCGSize:OldButtonBounds.size];
resizeAnimation.toValue = [NSValue valueWithCGSize:NewButtonBounds.size];
// Set Actual bubble size after animation
bubblename.bounds = NewButtonBounds;
// Setup Path
CGPoint MidPoint = CGPointMake(500,50);
CGPoint EndPoint = CGPointMake(RectPassed.origin.x, RectPassed.origin.y);
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.calculationMode = kCAAnimationPaced;
CGMutablePathRef curvedPath = CGPathCreateMutable();
CGPathMoveToPoint(curvedPath, nil, bubblename.bounds.origin.x, bubblename.bounds.origin.y);
CGPoint NewLoc = CGPointMake(745, 200);
CGPathMoveToPoint(curvedPath, NULL, NewLoc.x,NewLoc.y);
CGPathAddCurveToPoint(curvedPath, NULL, 745,200,MidPoint.x,MidPoint.y, EndPoint.x, EndPoint.y);
pathAnimation.path = curvedPath;
// Set Bubble action position after animation
bubblename.layer.position = point;
// Add scale and path to animation group
CAAnimationGroup *group = [CAAnimationGroup animation];
group.removedOnCompletion = YES;
[group setAnimations:[NSArray arrayWithObjects:pathAnimation, resizeAnimation, nil]];
group.duration = animationDuration;
[bubblename.layer addAnimation:group forKey:#"nil"]; //savingAnimation
CGPathRelease(curvedPath);
}
I call the animation for each bubble using
[self EmitBubbles:btnbubble1 :CGRectMake(200, 500, 84, 88)];
[self EmitBubbles:btnbubble2 :CGRectMake(200, 500, 84, 88)];
I have 20 bubbles on screen. The size of the rect passed is the final size I want the bubbles to be.
Can I can get each bubble to animate separately using a scale and keyframe path, without having to write the above code for each bubble?
Thanks
OK. I think I did it.After hours of looking around Stackoverflow and other places I found that to bhave the objects animate one after the other. I had to calculate the time according to the app time. I used.
group.beginTime = CACurrentMediaTime() + AnimationDelay
To stop the animation momentarily appearing at its final position before continuing with the animation. I had to use
group.fillMode = kCAFillModeBackwards;

CAKeyframeAnimation repeating rotation from last point

In my iphone app I have a UIButton that I rotate 180 degrees into view when another UIButton is pressed, then when clicked again the button rotates a further 180 degrees back to where it started.
This all works fine the very first time the complete 360 degree process occurs, but if I try to start from the start again it snaps 180 degrees then tries to rotate it from that point. Can anyone point me in the right direction? Here's my code so far...
showAnimation= [CAKeyframeAnimation animationWithKeyPath:#"transform.rotation"];
showAnimation.duration = self.showAnimationDuration;
showAnimation.repeatCount = 1;
showAnimation.fillMode = kCAFillModeForwards;
showAnimation.removedOnCompletion = NO;
showAnimation.cumulative = YES;
showAnimation.delegate = self;
float currentAngle =[[[rotateMe.layer presentationLayer] valueForKeyPath:#"transform.rotation.z"] floatValue];
//Rotate 180 degrees from current rotation
showAnimation.values = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:currentAngle],
[NSNumber numberWithFloat:currentAngle + (0.5 * M_PI)],
[NSNumber numberWithFloat:currentAngle + M_PI], nil];
[rotateMe.layer addAnimation:showAnimation forKey:#"show"];
On completion of the animation I then update the rotateMe.transform rotation to the layer's rotation so that it becomes usable.
- (void)animationDidStop:(CAKeyframeAnimation *)anim finished:(BOOL)flag
{
float currentAngle =[[[self.layer presentationLayer] valueForKeyPath:#"transform.rotation.z"] floatValue];
NSLog(#"End: %f", currentAngle);
rotateMe.transform = CGAffineTransformMakeRotation(0);
}
I have achieved the fully working effect with
[UIView animateWithDuration:1.0f]
animations:^{
CGAffineTransform transform = CGAffineTransformRotate(rotateMe.transform, DEGREES_TO_RADIANS(179.999f));
rotateMe.transform = transform;
}
];
But I'd like to make the animation more complex, hence the CAKeyframeAnimation.
You could configure the animation to be additive and animate from 0 to 180 degrees if you need all the key frames. If you don't need the different key frames the you can simply do it with a basic animation and the byValue property. The next time the animation is added it rotate 180 more degrees than whatever rotation is on the view.
If you are setting the actual value in the delegate callback then there is no need for fill mode and not removing the animation on completion.
CABasicAnimation *showAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
showAnimation.byValue = M_PI;
showAnimation.duration = self.showAnimationDuration;
showAnimation.delegate = self;
[rotateMe.layer addAnimation:showAnimation forKey:#"show"];
or using a key frame animation (as explained above: make it additive and animate from 0 to 180 degrees).
CAKeyframeAnimation *showAnimation = [CAKeyframeAnimation animationWithKeyPath:#"transform.rotation.z"];
showAnimation.additive = YES; // Make the values relative to the current value
showAnimation.values = #[0, /*all your intermediate values here... ,*/ M_PI];
showAnimation.duration = self.showAnimationDuration;
showAnimation.delegate = self;
[rotateMe.layer addAnimation:showAnimation forKey:#"show"];

Resources