CABasicAnimation comleted immediately after CALayer "setFrame", why? - objective-c

-(void)startAnimationWithTime:(CGFloat)animationTime
fromPoint:(CGPoint)fromPoint
toPoint:(CGPoint)toPoint
Completion:(SYBDanmakuAnimationCompletion)completion
{
[CATransaction begin];
[CATransaction setDisableActions:YES];
[CATransaction setCompletionBlock:^{
completion(YES);
}];
CABasicAnimation *mover = [CABasicAnimation animationWithKeyPath:#"position"];
[mover setDuration:animationTime];
[mover setFromValue:[NSValue valueWithCGPoint:fromPoint]];
[mover setToValue:[NSValue valueWithCGPoint:toPoint]];
[self addAnimation:mover forKey:#"move"];
[CATransaction commit];
}
This is the code of myLayer ,when I change the frame of its superLayer , this animation will complete immediately, who can tell me why? I want it to work correctly.

Related

CAShapeLayer Path Animation

I'm trying to animate a path change on a particular shape. I can manually change without animation by replacing this code with a simple .path [cgpath] setter. However, I want to animate the change for aesthetics.
When this code executes, there is no change to the shape what-so-ever although the log suggests the animation is complete.
for (CALayer *layer in self.view.layer.sublayers) {
if ([layer isKindOfClass:[CAShapeLayer class]]) {
CAShapeLayer *copy = (CAShapeLayer *)layer;
if ([sideChoice isEqualToString:#"datum"]) {
if ([copy.name isEqualToString:#"datumSideLayer"]) {
[CATransaction begin];
[CATransaction setCompletionBlock:^{
NSLog(#"Animation complete.");
}];
CABasicAnimation *morph = [CABasicAnimation animationWithKeyPath:#"path"];
morph.duration = 1;
morph.toValue = pathChange;
[copy addAnimation:morph forKey:nil];
[CATransaction commit];
break;
}
} else if ([sideChoice isEqualToString:#"opposite"]) {
if ([copy.name isEqualToString:#"oppositeSideLayer"]) {
}
}
}
}
for (CALayer *layer in self.view.layer.sublayers) {
if ([layer isKindOfClass:[CAShapeLayer class]]) {
CAShapeLayer *copy = (CAShapeLayer *)layer;
if ([sideChoice isEqualToString:#"datum"]) {
if ([copy.name isEqualToString:#"datumSideLayer"]) {
[CATransaction begin];
[CATransaction setCompletionBlock:^{
NSLog(#"Animation complete.");
}];
CABasicAnimation *morph = [CABasicAnimation animationWithKeyPath:#"path"];
morph.duration = 1;
morph.fromValue = (id) copy.path;
morph.toValue = (id) pathChange.CGPath;
[copy addAnimation:morph forKey:nil];
copy.path = [pathChange CGPath];
[CATransaction commit];
break;
}
} else if ([sideChoice isEqualToString:#"opposite"]) {
if ([copy.name isEqualToString:#"oppositeSideLayer"]) {
}
}
}
}
I didn't correctly set a fromValue nor was I pointing to a CGPath.

Piechart not rotating on tap in Coreplot- iOS

Please refer to the code for rotating the piechart when any sice is touched:
-(void)pieChart:(CPTPieChart *)plot sliceWasSelectedAtRecordIndex:(NSUInteger)index{
[self rotatePieChart:plot];
}
-(void)rotatePieChart:(CPTPieChart *)plot {
[CATransaction begin];
{
CABasicAnimation *rotation = [CABasicAnimation animationWithKeyPath:#"transform"];
CATransform3D transform = CATransform3DMakeRotation(-45, 0, 0, 1);
rotation.toValue = [NSValue valueWithCATransform3D:transform];
rotation.fillMode = kCAFillModeForwards;
rotation.duration = 1.0f;
rotation.cumulative=YES;
rotation.removedOnCompletion = NO;
[plot addAnimation:rotation forKey:#"rotation"];
}
[CATransaction commit];
}
ON first Tap the piechart rotates but on subsequent taps nothing happens.
Instead of rotating the whole plot layer, animate only the startAngle property. I suspect the hit-detection code is getting confused by the rotated coordinate system.

Animating a CALayer shadowpath

I'm animating the shadow path for CALayer.
The frame resizes correctly, but the shadow does not scale.
Instead the shadow starts at the final size CGSize(20,20) and holds throughout the animation even though I set the shadowPath to an initial value
[CATransaction begin];
[CATransaction setAnimationDuration: 0];
[CATransaction setDisableActions: TRUE];
layer.frame = CGRectMake(0,0,10,10);
layer.shadowPath = [UIBezierPath bezierPathWithRect:layer.bounds].CGPath;
[CATransaction commit];
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:10] forKey:kCATransactionAnimationDuration];
layer.frame = CGRectMake(0,0,20,20);
layer.shadowPath = [UIBezierPath bezierPathWithRect:tile.bounds].CGPath;
[CATransaction commit];
At first, small square with drop shadow.
When button pushed, square and shadow grow bigger together.
The main code is below:
[CATransaction begin];
[CATransaction setAnimationDuration:5.0];
CAMediaTimingFunction *timing = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[CATransaction setAnimationTimingFunction:timing];
layer.frame = CGRectMake(0,0,100,100);
[CATransaction commit];
CABasicAnimation *shadowAnimation = [CABasicAnimation animationWithKeyPath:#"shadowPath"];
shadowAnimation.duration = 5.0;
shadowAnimation.fromValue = (id)[UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 50, 50)].CGPath;
shadowAnimation.toValue = (id)[UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 100)].CGPath;
[layer addAnimation:shadowAnimation forKey:#"shadow"];
You can download this project from GitHub and just run it.
https://github.com/weed/p120812_CALayerShadowTest
This question was very hard for me ! :)
Wanted to add another answer based on Weed's answer. I took weed's answer and tried to put everything in a CATransaction because i wanted to animate multiple layers and be sure animations happened together. Here you guys go if you ever need it. Also, i still don't understand why you have to use the fromValue and toValue in a CATransaction. Why can't you just do the same thing you do with other properties like frame.
[CATransaction begin];
[CATransaction setValue:[CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionEaseOut]
forKey:kCATransactionAnimationTimingFunction];
for (CALayer *layer in self.layers){
CABasicAnimation *shadowAnimation =
[CABasicAnimation animationWithKeyPath:#"shadowPath"];
shadowAnimation.fromValue =
(id)[UIBezierPath bezierPathWithRect:layer.bounds].CGPath;
shadowAnimation.timingFunction =
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
layer.frame = rectFinal;
shadowAnimation.toValue =
(id)[UIBezierPath bezierPathWithRect:layer.bounds].CGPath;
layer.shadowPath =
[UIBezierPath bezierPathWithRect:layer.bounds].CGPath;
[layer addAnimation:shadowAnimation forKey:nil];
}
[CATransaction commit];

Slow down & accelerate animations with CoreAnimation

I am trying to apply a slow-motion effect to my application, pretty much like how you can slow down most graphical effects of Mac OS if you press Shift.
My application uses CoreAnimation, so I thought it should be no biggie: set speed to some slower value (like 0.1) and set it back to 1 once I'm done, and here I go.
It seems, unfortunately, that this is not the right way. The slowdown works great, however when I want to get back to normal speed, it resumes as if the speed was 1 the whole time. This basically means that if I held Shift for long enough, as soon as I release it, the animation instantly completes.
I found a Technical QA page explaining how to pause and resume an animation, but I can't seem to get it right if it's not about entirely pausing the animation. I'm definitely not very good at time warping.
What would be the right way to slow down then resume an animation with CoreAnimation?
Here's the useful code:
-(void)flagsChanged:(NSEvent *)theEvent
{
CALayer* layer = self.layer;
[CATransaction begin];
CATransaction.disableActions = YES;
layer.speed = (theEvent.modifierFlags & NSShiftKeyMask) ? 0.1 : 1;
[CATransaction commit];
}
Tricky problem...
I set up a test with a basic UIView.
I initiate an animation of its layer from it's current point to a target point.
In order to slow down the core animation, I actually had to construct and replace a new animation (since you can't modify the existing animation).
It's especially important to figure out how far along the current animation has already proceeded. This is done by accessing beginTime, currentTime, calculating the elapsed time and then figuring out how long should be the duration of the new animation.
- (void)initiate {
if(!initiated) {
[CATransaction begin];
[CATransaction disableActions];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut]];
CABasicAnimation *ba = [CABasicAnimation animationWithKeyPath:#"position"];
ba.duration = 10.0f;
ba.fromValue = [NSValue valueWithCGPoint:view.layer.position];
ba.toValue = [NSValue valueWithCGPoint:CGPointMake(384, 512)];
[view.layer addAnimation:ba forKey:#"animatePosition"];
[CATransaction commit];
initiated = YES;
}
}
- (void)slow {
[CATransaction begin];
CABasicAnimation *old = (CABasicAnimation *)[view.layer animationForKey:#"animatePosition"];
CABasicAnimation *ba = [CABasicAnimation animationWithKeyPath:#"position"];
CFTimeInterval animationBegin = old.beginTime;
CFTimeInterval currentTime = CACurrentMediaTime();
CFTimeInterval elapsed = currentTime - animationBegin;
ba.duration = [[old valueForKey:#"duration"] floatValue] - elapsed;
ba.duration = [[old valueForKey:#"duration"] floatValue];
ba.autoreverses = [[old valueForKey:#"autoreverses"] boolValue];
ba.repeatCount = [[old valueForKey:#"repeatCount"] floatValue];
ba.fromValue = [NSValue valueWithCGPoint:((CALayer *)[view.layer presentationLayer]).position];
ba.toValue = [old valueForKey:#"toValue"];
ba.speed = 0.1;
[view.layer addAnimation:ba forKey:#"animatePosition"];
[CATransaction commit];
}
- (void)normal {
[CATransaction begin];
CABasicAnimation *old = (CABasicAnimation *)[view.layer animationForKey:#"animatePosition"];
CABasicAnimation *ba = [CABasicAnimation animationWithKeyPath:#"position"];
CFTimeInterval animationBegin = old.beginTime;
CFTimeInterval currentTime = CACurrentMediaTime();
CFTimeInterval elapsed = currentTime - animationBegin;
ba.duration = [[old valueForKey:#"duration"] floatValue] - elapsed;
ba.autoreverses = [[old valueForKey:#"autoreverses"] boolValue];
ba.repeatCount = [[old valueForKey:#"repeatCount"] floatValue];
ba.fromValue = [NSValue valueWithCGPoint:((CALayer *)[view.layer presentationLayer]).position];
ba.toValue = [old valueForKey:#"toValue"];
ba.speed = 1;
[view.layer addAnimation:ba forKey:#"animatePosition"];
[CATransaction commit];
}
Note: The above code works only in 1 direction, not with animations that autoreverse...

combine multiple CAAnimation sequentially

I was reading about CATransactions and then thought this might help solve my problem.
This is what I wan't to do:
I have 3 animations in the same layer, which all have their own duration. I create the animations using CAKeyframeAnimation with a CGMutablePathRef.
say for example:
anim1 -> duration 5s
anim2 -> 3s
anim3 -> 10s
Now I want to serialize them sequentially. I tried to use CAAnimationGroup but the animations run concurrently.
I read about CATransaction, is it a possible solution? Can you give me a little example?
thanks for help !
If by serialization you mean starting each animation after the previous one has finished, use the beginTime property (defined in CAMediaTiming protocol). Note that its documentation is a little misleading. Here's an example:
anim2.beginTime = anim1.beginTime + anim1.duration;
anim3.beginTime = anim2.beginTime + anim2.duration;
If You are sure to do this thing with Layers then you may try like following
Using Completion Block in CATransactions
-(void)animateThreeAnimationsOnLayer:(CALayer*)layer animation:(CABasicAnimation*)firstOne animation:(CABasicAnimation*)secondOne animation:(CABasicAnimation*)thirdOne{
[CATransaction begin];
[CATransaction setCompletionBlock:^{
[CATransaction begin];
[CATransaction setCompletionBlock:^{
[CATransaction begin];
[CATransaction setCompletionBlock:^{
//If any thing on completion of all animations
}];
[layer addAnimation:thirdOne forKey:#"thirdAnimation"];
[CATransaction commit];
}];
[layer addAnimation:secondOne forKey:#"secondAnimation"];
[CATransaction commit];
}];
[layer addAnimation:firstOne forKey:#"firstAnimation"];
[CATransaction commit];
}
Another way is by applying delay to begin animation.
-(void)animateThreeAnimation:(CALayer*)layer animation:(CABasicAnimation*)firstOne animation:(CABasicAnimation*)secondOne animation:(CABasicAnimation*)thirdOne{
firstOne.beginTime=0.0;
secondOne.beginTime=firstOne.duration;
thirdOne.beginTime=firstOne.duration+secondOne.duration;
[layer addAnimation:firstOne forKey:#"firstAnim"];
[layer addAnimation:secondOne forKey:#"secondAnim"];
[layer addAnimation:thirdOne forKey:#"thirdAnim"];
}
And if You are going to use UIVIew Animation
//if View is applicable in your requirement then you can look this one;
-(void)animateThreeAnimationOnView{
[UIView animateWithDuration:2.0 animations:^{
//first Animation
} completion:^(BOOL finished) {
[UIView animateWithDuration:2.0 animations:^{
//Second Animation
} completion:^(BOOL finished) {
[UIView animateWithDuration:2.0 animations:^{
//Third Animation
}];
}];
}];
}