make animation continues on multi layers by using CAAnimation - ios

I'm creating a tutorial screen to let user understand what to do in the screen.
For example, I have viewPlug and viewBulb. First, viewPlug moves up until it collide viewBulb. After that viewBulb changes color to green to notify. After some seconds, viewPlug moves down and viewBulb changes to red immediately.
So far, I'm digging in UIViewAnimation and UIViewKeyframeAnimation but none of them give me full right to handle complex animation, duration, beginTime... Only CAAnimation can do exactly what I want in scenarios.
But I'm not sure if this is a best way to do in my case, or iOS supports other methods that easier/simpler to handle?
Here is my sample code.
- (void)makeAnimations {
// Group all animation into 1 transaction
[CATransaction begin];
// Create plug animation
CABasicAnimation *animatePlug = [CABasicAnimation animationWithKeyPath:#"position.y"];
animatePlug.beginTime = 0;
animatePlug.byValue = #(-180);
animatePlug.duration = 1.5;
animatePlug.fillMode =kCAFillModeForwards;
animatePlug.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
// Create unplug animation
CABasicAnimation *animateUnplug = [CABasicAnimation animationWithKeyPath:#"position.y"];
animateUnplug.beginTime = 4;
animateUnplug.byValue = #100;
animateUnplug.duration = 0.25;
animateUnplug.fillMode =kCAFillModeForwards;
animateUnplug.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
// Group all animation of view A
CAAnimationGroup *animateGroupPosition = [CAAnimationGroup animation];
animateGroupPosition.duration = 6;
animateGroupPosition.animations = #[animatePlug, animateUnplug];
animateGroupPosition.repeatCount = HUGE_VALF;
[self.viewPlug.layer addAnimation:animateGroupPosition forKey:#"position"];
// Create plug color animation
CABasicAnimation *animateGreen = [CABasicAnimation animationWithKeyPath:#"backgroundColor"];
animateGreen.toValue = (__bridge id)([UIColor greenColor].CGColor);
animateGreen.beginTime = 1.5;
animateGreen.duration = 0.5;
animateGreen.fillMode =kCAFillModeForwards;
// Create unplug color animation
CABasicAnimation *animateRed = [CABasicAnimation animationWithKeyPath:#"backgroundColor"];
animateRed.toValue = (__bridge id)([UIColor redColor].CGColor);
animateRed.beginTime = 4;
animateRed.duration = 0.1;
animateRed.fillMode =kCAFillModeForwards;
// Group all animation of view B
CAAnimationGroup *animateGroupColor = [CAAnimationGroup animation];
animateGroupColor.duration = 6;
animateGroupColor.animations = #[animateGreen, animateRed];
animateGroupColor.repeatCount = HUGE_VALF;
[self.viewBulb.layer addAnimation:animateGroupColor forKey:#"color"];
[CATransaction commit];
}

Related

I set the toValue of rotation to 2*PI, but why after the animation these views are not horizontal?

Codes are as follows:
CABasicAnimation *rotateWhenBoom = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotateWhenBoom.fromValue = #0;
rotateWhenBoom.toValue = #(M_PI*2.0);
rotateWhenBoom.duration = 0.3f;
rotateWhenBoom.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
CAAnimationGroup *boom = [CAAnimationGroup animation];
boom.animations = #[[self moveOutWithBtn:btn], rotateWhenBoom];
boom.fillMode = kCAFillModeForwards;
boom.removedOnCompletion = NO;
And the effect:
effect
Before the animation the views are all horizontal. I add a rotation animation to these views, and the toValue of rotation is 2*PI. Why after the animation they are not horizontal?
Thanks in advance!!
Your last two lines of code:
boom.fillMode = kCAFillModeForwards;
boom.removedOnCompletion = NO;
mean that the layer stays at the final state of animation and that the animation is not automatically removed from the layer when it completes. You probably want to remove the last line boom.removeOnCompletion = NO
A good guide for CALayer and CAAnimation that explains this: https://www.objc.io/issues/12-animations/animations-explained/

CAAnimationGroup Running Too Fast

My CAAnimationGroup is running too fast. I want it to take a total of 50 seconds to do something, and it is doing it in about 3...it breezes through the first animation, and does the other one slow.
hover = [CABasicAnimation animationWithKeyPath:#"position"];
hover.fillMode = kCAFillModeForwards;
hover.removedOnCompletion = NO;
hover.additive = YES; // fromValue and toValue will be relative instead of absolute values
hover.fromValue = [NSValue valueWithCGPoint:CGPointZero];
hover.toValue = [NSValue valueWithCGPoint:CGPointMake(26*22, 26*-10.0)]; // y increases downwards on iOS
hover.autoreverses = FALSE; // Animate back to normal afterwards
hover.repeatCount = 0; // The number of times the animation should repeat
CABasicAnimation *fall = [CABasicAnimation animationWithKeyPath:#"position"];
fall.fillMode = kCAFillModeForwards;
fall.removedOnCompletion = NO;
fall.additive = YES; // fromValue and toValue will be relative instead of absolute values
fall.fromValue = [NSValue valueWithCGPoint:CGPointMake(26*22, 26*-10.0)];
fall.toValue = [NSValue valueWithCGPoint:CGPointMake(26*22, guess1*700.0)]; // y increases downwards on iOS
fall.autoreverses = FALSE; // Animate back to normal afterwards
fall.repeatCount = 0; // The number of times the animation should repeat
CAAnimationGroup* group = [CAAnimationGroup new];
group.beginTime = 0.;
[group setDuration:50.0];
group.animations = #[ hover, fall ];
[theDude.layer addAnimation:group forKey:#"myHoverAnimation"];
Judging by your implementation, I'm assuming you want a sequence of animations on an object theDude on it's position property.
You can achieve the animation by using CATransaction using it's completion blocks. More can be read here.

Two animations on one layer

I have a CALayer, and I want to show it and then hide.
CALayer *layerOne = [CALayer layer];
[layerOne addSublayer:textOne];
layerOne.frame = CGRectMake(0, 0, size.width, size.height);
[layerOne setMasksToBounds:YES];
layerOne.opacity = 0.0;
CABasicAnimation *animationOne = [CABasicAnimation animationWithKeyPath:#"opacity"];
[animationOne setDuration:0];
[animationOne setFromValue:[NSNumber numberWithFloat:0.0]];
[animationOne setToValue:[NSNumber numberWithFloat:1.0]];
[animationOne setBeginTime:3];
[animationOne setRemovedOnCompletion:NO];
[animationOne setFillMode:kCAFillModeForwards];
[layerOne addAnimation:animationOne forKey:#"animateOpacity"];
This code work successfully, layerOne appear after 3 seconds.
But I want to hide this layer, so I add this:
CABasicAnimation *animationTwo = [CABasicAnimation animationWithKeyPath:#"opacity"];
[animationTwo setDuration:0];
[animationTwo setFromValue:[NSNumber numberWithFloat:1.0]];
[animationTwo setToValue:[NSNumber numberWithFloat:0.0]];
[animationTwo setBeginTime:6];
[animationTwo setRemovedOnCompletion:NO];
[animationTwo setFillMode:kCAFillModeForwards];
[layerOne addAnimation:animationTwo forKey:#"animateOpacity"];
And it doesn't work. layerOne not appear after 3 seconds. Its just flashed in second 6 and disappear. Its seems like the second animation blocking the first one and only the second animation is going on.
What I do wrong?
Well, for one thing since the second animation has the same key, when you add it to the layer, the original animation will be removed. When the animation is removed, it's long-term effect (setting opacity = 1.0) will also be removed so the animation will be immediately hidden.
For something like this, the normal process to show the layer is:
// set the final result you want to persist forever
layerOne.opacity = 1.0;
// set up your animation here
CABasicAnimation *animationOne = [CABasicAnimation animationWithKeyPath:#"opacity"];
animationOne.fromValue = #(0.);
animationOne.toValue = #(1.);
animationOne.duration = 3.;
animationOne.beginTime = 0.;
animationOne.removedOnCompletion = true;
animationOne.fillMode = kCAFillModeRemove; // for clarity, this is the default
[layerOne addAnimation:animationOne forKey:#"animateOpacity"];
And then when you want to hide the layer just reverse the process:
// Set the final animation state
layerOne.opacity = 0.0;
// set up your animation here
CABasicAnimation *animationTwo = [CABasicAnimation animationWithKeyPath:#"opacity"];
animationTwo.fromValue = #(1.);
animationTwo.toValue = #(0.);
animationTwo.duration = 3.;
animationTwo.beginTime = 0.;
animationTwo.removedOnCompletion = true;
animationTwo.fillMode = kCAFillModeRemove; // for clarity, this is the default
[layerOne addAnimation:animationTwo forKey:#"animateOpacity"];
If you're wanting to run the whole process as a single event, you should put both animations into a single animation group:
// set up your animation here
CABasicAnimation *animationOne = [CABasicAnimation animationWithKeyPath:#"opacity"];
animationOne.fromValue = #(0.);
animationOne.toValue = #(1.);
animationOne.duration = 3.;
animationOne.beginTime = 0.;
animationOne.fillMode = kCAFillModeForwards;
// set up your animation here
CABasicAnimation *animationTwo = [CABasicAnimation animationWithKeyPath:#"opacity"];
animationTwo.fromValue = #(1.);
animationTwo.toValue = #(0.);
animationTwo.beginTime = 6.;
animationTwo.duration = 3.;
animationOne.fillMode = kCAFillModeForwards;
// set up the animation group
CAAnimationGroup* group = [CAAnimationGroup new];
group.beginTime = 0.;
group.duration = 9.;
group.animations = #[ animationOne, animationTwo ];
[layerOne addAnimation:group forKey:#"animateOpacity"];

Loop animation - doesn't go

I'm trying to repeat a sequence of animations, inside a LOOP, changing at each loop some parameters randomly. Here is the code. Anyone please knows why it doesn't work?
If I call it once with a button action, it works, but with a loop it doesn't.
Thanks a lot! Giuseppe
-(IBAction)startLoop:(id)sender {
for (int i=1;i<10; i++) {
[self animation2];
}
}
-(id) animation2 {
int max=500;
UIImage *myImage = [UIImage imageNamed:#"coccinella2.png"];
CALayer *myLayer = [CALayer layer];
myLayer.contents = (id)myImage.CGImage;
myLayer.bounds = CGRectMake(0, 0, 50, 60);
[myLayer setPosition:CGPointMake(arc4random()%(max), arc4random()%(max))];
[myLayer setBounds:CGRectMake(0.0, 0.0, 50.0, 60.0)];
[self.view.layer addSublayer:myLayer];
//translation1
CGPoint startPt = CGPointMake(arc4random()%(max),arc4random()%(max));
CGPoint endPt = CGPointMake(arc4random()%(max),arc4random()%(max));
CABasicAnimation *transl1 = [CABasicAnimation animationWithKeyPath:#"position"];
transl1.removedOnCompletion = FALSE;
transl1.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
transl1.fromValue = [NSValue valueWithCGPoint:startPt];
transl1.toValue = [NSValue valueWithCGPoint:endPt];
transl1.duration = 2.0;
transl1.fillMode = kCAFillModeForwards;
transl1.beginTime = 0;
//scale 1
CABasicAnimation *scale1 = [CABasicAnimation
animationWithKeyPath:#"transform.scale"];
scale1.removedOnCompletion = FALSE;
[scale1 setToValue:[NSNumber numberWithInt:3]];
[scale1 setDuration:2.0f];
scale1.fillMode = kCAFillModeForwards;
scale1.beginTime = 0;
//rotation1
CABasicAnimation *rotation1 = [CABasicAnimation
animationWithKeyPath:#"transform.rotation.z"];
rotation1.removedOnCompletion = FALSE;
[rotation1 setFromValue:DegreesToNumber(0)];
[rotation1 setToValue:DegreesToNumber(90)];
//rotation1.repeatCount = HUGE_VALF;
[rotation1 setDuration:2.0f];
rotation1.fillMode = kCAFillModeForwards;
rotation1.beginTime = 0;
//group
CAAnimationGroup* group = [CAAnimationGroup animation];
[group setDuration: 6.0];
group.removedOnCompletion = FALSE;
group.fillMode = kCAFillModeForwards;
[group setAnimations: [NSArray arrayWithObjects:scale1, transl1, rotation1, nil]];
[myLayer addAnimation: group forKey: nil];
}
Your code doesn't repeat the annotation 10 times but starts 10 animations right away. If your goal is to start the animations after the previous one ended you should try using an NSTimer.
You can use an NSTimer to trigger each group of animations at a different time, or you can set the beginTime on each group, with code like this:
group.beginTime = CACurrentMediaTime() + delay;
For animations, using the beginTime gives more accurate timing, since CA is run on a separate thread and doesn't stall like NSTimers if your app gets busy.

CAAnimationGroup reverts to original position on completion

In iOS I'm trying to create the effect of an icon shrinking in size, and flying across the screen in an arc while fading out, and then disappearing. I've achieved these 3 effects with an CAAnimationGroup, and it does what I want. The problem is when the animation ends, the view appears back at the original position, full size and full opacity. Can anyone see what I am doing wrong in the code below?
The animation should not revert to it's original position, but just disappear at the end.
UIBezierPath *movePath = [UIBezierPath bezierPath];
CGPoint libraryIconCenter = CGPointMake(610, 40);
CGPoint ctlPoint = CGPointMake(self.imgViewCropped.center.x, 22.0);
movePath moveToPoint:self.imgViewCropped.center];
[movePath addQuadCurveToPoint:libraryIconCenter
controlPoint:ctlPoint];
CAKeyframeAnimation *moveAnim = [CAKeyframeAnimation animationWithKeyPath:#"position"];
moveAnim.path = movePath.CGPath;
moveAnim.removedOnCompletion = NO;
CABasicAnimation *scaleAnim = [CABasicAnimation animationWithKeyPath:#"transform"];
scaleAnim.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
scaleAnim.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.1, 0.1, 1.0)];
scaleAnim.removedOnCompletion = NO;
CABasicAnimation *opacityAnim = [CABasicAnimation animationWithKeyPath:#"alpha"];
opacityAnim.fromValue = [NSNumber numberWithFloat:1.0];
opacityAnim.toValue = [NSNumber numberWithFloat:0.0];
opacityAnim.removedOnCompletion = NO;
CAAnimationGroup *animGroup = [CAAnimationGroup animation];
animGroup.animations = [NSArray arrayWithObjects:moveAnim,scaleAnim,opacityAnim, nil];
animGroup.duration = 0.6;
animGroup.delegate = self;
animGroup.removedOnCompletion = NO;
[self.imgViewCropped.layer addAnimation:animGroup forKey:nil];
I believe you need to set the fillMode property of your animations to kCAFillModeForwards. That should freeze the animations at their end time. Another suggestion (and honestly, this is what I'd usually do) is just se the properties of the layer itself to their final position after you've set up the animation. That way when the animation is removed, the layer will still have the final properties as part of its model.
As an aside, the removedOnCompletion flag of animations contained within a CAAnimationGroup is ignored. You should probably just remove those assignments since they're misleading. Replace them with assignments to fillMode as specified above.

Resources