I have a problem with CAAnimationGroup. I have a view called animeView and I want to apply a 3D rotation around Y axis and a scaling transform at the same time. The rotation and scaling animations work perfectly fine separately! But when I add them to a CAAnimationGroup Non of the animations occur!
What is the problem?
CAKeyframeAnimation *rotation = [CAKeyframeAnimation animation];
CATransform3D rotationTransform = CATransform3DMakeRotation(M_PI, 0.0f, 1.0f, 0.0f);
rotationTransform.m34 = 1.0 / -500;
rotation.values = [NSArray arrayWithObjects:
[NSValue valueWithCATransform3D:CATransform3DMakeRotation(0.0f, 0.0f, 1.0f, 0.0f)],
[NSValue valueWithCATransform3D:rotationTransform],
[NSValue valueWithCATransform3D:CATransform3DMakeRotation(2.0*M_PI, 0.0f, 1.0f, 0.0f)] ,nil];
CAKeyframeAnimation *trans = [CAKeyframeAnimation animation];
trans.values = [NSArray arrayWithObjects:[NSValue valueWithCATransform3D:CATransform3DIdentity],[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.5, 0.5, 0.5)],[NSValue valueWithCATransform3D:CATransform3DIdentity], nil];
CAAnimationGroup *group = [CAAnimationGroup animation];
group.delegate = self;
[group setAnimations: [NSArray arrayWithObjects:rotation, trans, nil]];
group.duration = 2;
group.repeatCount = 3;
[self.animView.layer addAnimation:group forKey:nil];
There are two things that is strange with the code you've provided.
The animations doesn't know what property they should be changing (no key path is set)
Both animations are changing the transform (what value should really be used?)
The way I see it is that you have two transform key-frame animations with the same number of values (three). Therefore you should calculate the total transformation matrix (both scale and rotate in one matrix) and only add that animation to your view.
It would look something like this (I've added lots of comments to explain what sis happening):
// Your first rotation is 0 degrees and no scaling
CATransform3D beginTransform = CATransform3DIdentity;
// Your middle rotation is the exact one that you provided ...
CATransform3D middleTransform = CATransform3DMakeRotation(M_PI, 0.0f, 1.0f, 0.0f);
// ... but the rotation matrix is then scaled as well ...
middleTransform = CATransform3DScale(middleTransform, 0.5, 0.5, 0.5);
// ... and the perspective is set
middleTransform.m34 = 1.0 / -500;
// Your end value is rotated but not scaled
CATransform3D endTransform = CATransform3DMakeRotation(2.0*M_PI, 0.0f, 1.0f, 0.0f);
// Create a new animation that should animate the "transform" property (thus the key path "transform")
CAKeyframeAnimation *rotateAndScale = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
// same configurations as your group had
[rotateAndScale setDuration:2.0];
[rotateAndScale setRepeatDuration:3.0];
[rotateAndScale setDelegate:self];
// Set the values of the transform animation (yes, both scaling and rotation in one animation)
[rotateAndScale setValues:[NSArray arrayWithObjects:
[NSValue valueWithCATransform3D:beginTransform],
[NSValue valueWithCATransform3D:middleTransform],
[NSValue valueWithCATransform3D:endTransform], nil]];
// Add the animation to the layer, no need for a group here...
[animView.layer addAnimation:rotateAndScale forKey:nil];
Related
I'm trying to shrink a circular CAShapeLayer but using the key path 'path' does not seem to be working.
CGPathRef startPath = [UIBezierPath bezierPathWithOvalInRect:startRect].CGPath;
CGPathRef endPath = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(startRect, 15, 15)].CGPath;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.duration = 1.0;
animation.fromValue = (__bridge id)startPath;
animation.toValue = (__bridge id)endPath;
animation.autoreverses = YES;
animation.repeatCount = CGFLOAT_MAX;
animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.6 :0.3 :0.8 :0.45];
//circleLayer is a CAShapeLayer
[self.circleLayer addAnimation:animation forKey:#"pathAnimation"];
I think i must be misunderstanding about how a path animation works because pretty much identical code seems to work fine if i am try to animate, say, the opacity or the transform.
Any ideas?
I recently had the same question and ended up solving it by creating the circle, then animating the scale CGAffineTransform. In the view whose layer is the circle, I simply used
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformScale(transform, scaleX, scaleY);
[UIView animateWithDuration: 0.5 animations: ^{
self.transform = transform;
}];
Hope this helps!
Trying to animatie an ellipse masked on a UIView to be scale transformed remaining in the center position.
I have found CALayer - CABasicAnimation not scaling around center/anchorPoint, and followed by adding a bounds property to the maskLayer CAShapeLayer however, this ends with the mask being positioned in the left corner with only 1/4 of it showing. I would like the mask to remain within the center of the screen.
#synthesize maskedView;
- (void)viewDidLoad
{
[super viewDidLoad];
UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"Next"];
vc.view.frame = CGRectMake(0,0, self.view.frame.size.width, self.view.frame.size.height);
vc.view.layer.bounds = self.view.layer.bounds;
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
CGRect maskRect = CGRectMake((self.view.frame.size.width/2)-50, (self.view.frame.size.height/2)-50, 100, 100);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddEllipseInRect(path, nil, maskRect);
[maskLayer setPath:path];
CGPathRelease(path);
vc.view.layer.mask = maskLayer;
[self.view addSubview:vc.view];
maskedView = vc.view;
[self startAnimation];
}
Animation...
-(void)startAnimation
{
maskedView.layer.mask.bounds = CGRectMake((self.view.frame.size.width/2)-50, (self.view.frame.size.height/2)-50, 100, 100);
maskedView.layer.mask.anchorPoint = CGPointMake(.5,.5);
maskedView.layer.mask.contentsGravity = #"center";
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
animation.duration = 1.0f;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fromValue = [NSNumber numberWithFloat:1.0f];
animation.toValue = [NSNumber numberWithFloat:5.0f];
[maskedView.layer.mask addAnimation:animation forKey:#"animateMask"];
}
Update
I seem to have fixed this with a second animation on the position key. Is this correct or is there a better way of doing this?
CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:#"position"];
animation2.duration = 1.0f;
animation2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation2.fromValue = [NSValue valueWithCGPoint:CGPointMake((self.view.frame.size.width/2), (self.view.frame.size.height/2))];
animation2.toValue = [NSValue valueWithCGPoint:CGPointMake((self.view.frame.size.width/2), (self.view.frame.size.height/2))];
[toBeMask.layer.mask addAnimation:animation2 forKey:#"animateMask2"];
You can create a scale around the center of the object instead of (0, 0) like this:
CABasicAnimation *scale = [CABasicAnimation animationWithKeyPath:#"transform"];
CATransform3D tr = CATransform3DIdentity;
tr = CATransform3DTranslate(tr, self.bounds.size.width/2, self.bounds.size.height/2, 0);
tr = CATransform3DScale(tr, 3, 3, 1);
tr = CATransform3DTranslate(tr, -self.bounds.size.width/2, -self.bounds.size.height/2, 0);
scale.toValue = [NSValue valueWithCATransform3D:tr];
So we start out with the "identity" transform (which means 'no transform'). Then we translate (move) to the center of the object. Then we scale. Then we move back to origo so we're back where we started (we only wanted to affect the scale operation).
I seem to have fixed this with a second animation on the position key. Is this correct or is there a better way of doing this?
CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:#"position"];
animation2.duration = 1.0f;
animation2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation2.fromValue = [NSValue valueWithCGPoint:CGPointMake((self.view.frame.size.width/2), (self.view.frame.size.height/2))];
animation2.toValue = [NSValue valueWithCGPoint:CGPointMake((self.view.frame.size.width/2), (self.view.frame.size.height/2))];
[toBeMask.layer.mask addAnimation:animation2 forKey:#"animateMask2"];
I am not sure why, but CAShapeLayer does not set the bounds property to the layer. In my case, that's was the problem.
Try setting the bounds. That should work.
I did something like this for building the initial circle
self.circle = [CAShapeLayer layer];
CGRect roundedRect = CGRectMake(0, 0, width, width);
self.circle.bounds = roundedRect;
UIBezierPath *start = [UIBezierPath bezierPathWithRoundedRect:roundedRect cornerRadius:width/2];
[self.circle setPath:start.CGPath];
self.circle.position = CGPointMake(whatever suits you);
self.circleView.layer.mask = self.circle;
[self.circleView.layer.mask setValue: #(1) forKeyPath: #"transform.scale"];
And something like this for scaling it
CABasicAnimation *scale = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
scale.fromValue = [self.circleView.layer.mask valueForKeyPath:#"transform.scale"];
scale.toValue = #(100);
scale.duration = 1.0;
scale.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self.circleView.layer.mask setValue:scale.toValue forKeyPath:scale.keyPath];
[self.circleView.layer.mask addAnimation:scale forKey:scale.keyPath];
PD: AnchorPoint is by default (.5,.5) according to apple documentations...but we all know that does not mean anything right?
Hope it helps!!
I have written following function with many added tweaks and options, could be useful for many others.
func layerScaleAnimation(layer: CALayer, duration: CFTimeInterval, fromValue: CGFloat, toValue: CGFloat) {
let timing = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
let scaleAnimation: CABasicAnimation = CABasicAnimation(keyPath: "transform.scale")
CATransaction.begin()
CATransaction.setAnimationTimingFunction(timing)
scaleAnimation.duration = duration
scaleAnimation.fromValue = fromValue
scaleAnimation.toValue = toValue
layer.add(scaleAnimation, forKey: "scale")
CATransaction.commit()
}
Note: Don't forget to update size after animation completion, because CATransaction reverts back to actual size.
I have the following code that rotates a CALayer by -45degrees on the Y axis:
#define D2R(x) (x * (M_PI/180.0))
- (void) swipe:(UISwipeGestureRecognizer *)recognizer
{
CATransform3D transform = CATransform3DMakeRotation(D2R(-45), 0, 1.0, 0);
transform.m34 = -1.0 / 850;
CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath: #"transform"];
transformAnimation.fillMode = kCAFillModeForwards;
transformAnimation.removedOnCompletion = NO;
transformAnimation.toValue = [NSValue valueWithCATransform3D:transform];
transformAnimation.duration = 0.5;
[self.layer addAnimation:transformAnimation forKey:#"transform"];
}
The animation works, except it ends with no perspective - ignoring my m34 setting if I'm understanding things correctly.
Halfway through:
At the end:
What am I doing wrong?
Animation only affects the appearance of the view during the animation. It doesn't get applied to the view after the animation ends. You need to do this yourself. I'm guessing something like this right after adding the animation will work:
self.layer.transform = transform;
You can do this right away, as the animation will hide it until the animation completes.
Try this :
- (void) swipe:(UISwipeGestureRecognizer *)recognizer
{
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -10 / 850.0;
transform = CATransform3DRotate(transform, D2R(-45), 0, 1.0, 0);
CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath: #"transform"];
transformAnimation.fillMode = kCAFillModeForwards;
transformAnimation.removedOnCompletion = NO;
transformAnimation.toValue = [NSValue valueWithCATransform3D:transform];
transformAnimation.duration = 0.5;
[self.layer addAnimation:transformAnimation forKey:#"transform"];
}
And end the effect is like this :
Basically, I'm rotating a layer about a point as :
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0 / -500;
transform = CATransform3DTranslate(transform, rotationPoint.x-center.x, rotationPoint.y-center.y, 0.0);
transform = CATransform3DRotate(transform, (rotationAngleFor) * M_PI / 180.0f, 0.0, 1.0, 0);
transform = CATransform3DTranslate(transform, center.x-rotationPoint.x, center.y-rotationPoint.y, 0.0);
I also create a layer, add it to a bigger layer and then apply the transform to it:
[self.layer addSublayer:myLayer];
myLayer.transform = transform;
How to animate this?
Note- Putting this in a UIView animation block doesn't work.
Edit: As #DuncanC pointed out my description did not match the actual code.
You can use a CABasicAnimation that is added to the layer as follows.
CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath: #"transform"];
<here goes your definition of transform>
transformAnimation.toValue = [NSValue valueWithCATransform3D:newTransform];
transformAnimation.duration = 10;
[self.layer addAnimation:transformAnimation forKey:#"transform"];
This animation performs a continuous change of the transform property of the layer from the original value of the transform property of the layer to the newTransform transformation. The change takes 10 seconds.
I am using a simple transformation animation to an UIIMageView.
[UIView animateWithDuration:2 delay:0 options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionCurveEaseInOut animations:^{
CGAffineTransform translate = CGAffineTransformMakeTranslation(0, -[self percentFromHeight:20]);
clueImage.transform = translate;
} completion:^(BOOL finished) {
[self.cluWordSelectionView setaWordsObjects:[NSArray arrayWithArray:[self.whiteCard clueWordObjectsForSoundAtIndex:self.index]]];
[self addSubview:cluWordSelectionView];
[clueImage.layer addAnimation:[Animations shakeAnimation] forKey:#"shake"];
}];
As you can see after the completion I am adding an other shake animation to the view:
[clueImage.layer addAnimation:[Animations shakeAnimation] forKey:#"shake"];
Which looks like that:
+(CAAnimation*)shakeAnimation {
CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
CGFloat wobbleAngle = 0.06f;
NSValue* valLeft = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(wobbleAngle, 0.0f, 0.0f, 1.0f)];
NSValue* valRight = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(-wobbleAngle, 0.0f, 0.0f, 1.0f)];
animation.values = [NSArray arrayWithObjects:valLeft, valRight, nil];
animation.beginTime = 3;
animation.autoreverses = YES;
animation.duration = 0.125;
animation.repeatCount = HUGE_VALF;
return animation;
}
The problem is that when the shakeAnimation starts the view "jumps" back to it's original position. How can I prevent that and Why this is happening?
Thanks
shani
The new animation should take the current transform (translation) into account:
CATransform3D leftTransform = CATransform3DMakeRotation(wobbleAngle, 0.0f, 0.0f, 1.0f);
leftTransform = CATransform3DConcat(layer.transform, leftTransform);
CATransform3D rightTransform = CATransform3DMakeRotation(-wobbleAngle, 0.0f, 0.0f, 1.0f);
rightTransform = CATransform3DConcat(layer.transform, rightTransform);
NSValue* valLeft = [NSValue valueWithCATransform3D:leftTransform];
NSValue* valRight = [NSValue valueWithCATransform3D:rightTransform];
Where layer is the layer that will be animated.