I am trying to make an animation where two layers move to the side, scale down, and rotate a little bit in 3D, all at the same time (then move back with the layer previously at the bottom not on top). I tried several methods, but none seem to work.
I have the 3d transform animation like so:
perspectiveTransformLeft = CATransform3DIdentity;
perspectiveTransformLeft.m34 = 1.0 / 500;
perspectiveTransformLeft = CATransform3DRotate(perspectiveTransformLeft, 35.0f * M_PI / 360.0f, 0.0f, 1.0f, 0.0f);
I've tried adding a scale transform that didn't work:
perspectiveTransformLeft = CATransform3DMakeScale(0.75, 0.75, 1);
I've tried to scale the layer in an animation block, but that didn't work either:
[UIView animateWithDuration:1.0f
delay:0.0f
options: UIViewAnimationOptionCurveEaseInOut
animations:^{
endingLayer.frame = CGRectMake(20.0f, 0.0f, 724.0f, 538.0f);
switchViewBottom.layer.transform = perspectiveTransformRight;
}
completion:^(BOOL finished){
[delegate switchAnimationFinished];
}
];
I am at a loss. Can someone help me?
Do some reading on CAAnimationGroup and use CABasicAnimations instead.
That should held you achieve what you're after. I'll search for an example in my code (I previously used it) if you'll have issues implementing it.
Edit: Here's some code
typedef void (^animationCompletionBlock)(void);
typedef void (^animationStartedBlock)(void);
- (void)addAnimations:(NSArray *)animations withDuration:(CGFloat)animationDuration onView:(UIView *)aView {
animationStartedBlock startBlock = ^void(void) {
// Additional Animation start code here
};
animationCompletionBlock endBlock = ^void(void) {
// Additional animation completed code here
};
CAAnimationGroup *group = [CAAnimationGroup animation];
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
group.duration = animationDuration;
[group setAnimations:animations];
group.delegate = self;
[group setValue:startBlock forKey:#"animationStartedBlock"];
[group setValue:endBlock forKey:#"animationCompletionBlock"];
[aView.layer addAnimation:group forKey:#"yourAnimationName"];
}
This will have your completion blocks called in your delegate
// Animation Delegate
- (void)animationDidStart:(CAAnimation *)anim {
animationStartedBlock animationStartedBlock = [anim valueForKey:#"animationStartedBlock"];
if (animationStartedBlock) {
animationStartedBlock();
}
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
animationCompletionBlock animationCompleteBlock = [theAnimation valueForKey:#"animationCompletionBlock"];
if (animationCompleteBlock) {
animationCompleteBlock();
}
}
How you create the animations and add them to an array to pass to this method is up to you, depending on what animations you want.
This is an example for two scale / fade animations:
// Scale
- (CABasicAnimation *)scaleAnimationForImageView:(UIImageView *)imageView withDuration:(CGFloat)duration {
CGRect imageFrame = imageView.frame;
CABasicAnimation *resizeAnimation = [CABasicAnimation animationWithKeyPath:#"bounds.size"];
[resizeAnimation setToValue:[NSValue valueWithCGSize:CGSizeMake(40.0f, imageFrame.size.height * (40.0f / imageFrame.size.width))]];
resizeAnimation.fillMode = kCAFillModeForwards;
resizeAnimation.duration = duration;
resizeAnimation.removedOnCompletion = NO;
return resizeAnimation;
}
// Fade
- (CABasicAnimation *)fadeAnimationWithFinalOpacity:(CGFloat)opacity withDuration:(CGFloat)duration {
CABasicAnimation *fadeOutAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
[fadeOutAnimation setToValue:[NSNumber numberWithFloat:opacity]];
fadeOutAnimation.fillMode = kCAFillModeForwards;
fadeOutAnimation.removedOnCompletion = NO;
fadeOutAnimation.duration = duration;
return fadeOutAnimation;
}
Related
I have two UIImages one over another, with the same size (40 by 40) it's a simple dot on the whole UIImage and I want to animate rotating as a 3D. When the first UIImage "colapse" and it is not visible, change the property HIDDEN to YES, and immediately the UIImage under the first begin to appears, and when the user press the button again everything turn back at the first state. But i works only the first time, after that, just appears and disappears the images.
Heres is my code:
#interface ViewController ()
{
BOOL myFlag;
}
#end
- (void)viewDidLoad {
[super viewDidLoad];
self.C002.image = [Test imageOfCircle002];
self.C005.image = [Test imageOfCircle005];
self.C005.hidden = YES;
myFlag = YES;
}
- (IBAction)animationButton:(id)sender
{
if (myFlag)
{
//First state of the UIImages,
[UIView animateWithDuration:1.0 animations:^{
//3D rotation C002
CABasicAnimation* animation;
animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
animation.fromValue = #(0);
animation.toValue = #(0.5 * M_PI);
animation.repeatCount = 1;
animation.duration = 1.0;
[self.C002.layer addAnimation:animation forKey:#"rotation"];
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0 / 500.0;
self.C002.layer.transform = transform;
} completion:^(BOOL finished) {
self.C002.hidden = YES; //Hide the first image
self.C005.hidden = NO; //Show the second and animate
[UIView animateWithDuration:1.0 animations:^{
//3D rotation C005
CABasicAnimation* animation2 = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
animation2.fromValue = #(0.5 * M_PI); //begin where the first UIImage ends
animation2.toValue = #(1.0 * M_PI);
animation2.repeatCount = 1;
animation2.duration = 1.0;
[self.C005.layer addAnimation:animation2 forKey:#"rotation"];
CATransform3D transform2 = CATransform3DIdentity;
transform2.m34 = 1.0 / 500.0;
self.C005.layer.transform = transform2;
} completion:^(BOOL finished) {
[self.C002.layer removeAnimationForKey:#"rotation"];
[self.C005.layer removeAnimationForKey:#"rotation"];
}];
}];
myFlag = NO; // Set the flag to other state.
}
else
{ //Heres try to turn everything back, but... not working.
[UIView animateWithDuration:1.0 animations:^{
//3D rotation C005
CABasicAnimation* animation;
animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
animation.fromValue = #(1.0 * M_PI); //begin where is in the last animation
animation.toValue = #(0.5 * M_PI);
animation.repeatCount = 1;
animation.duration = 1.0;
[self.C005.layer addAnimation:animation forKey:#"rotation"];
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0 / 500.0;
self.C005.layer.transform = transform;
} completion:^(BOOL finished) {
self.C005.hidden = YES;
self.C002.hidden = NO;
[UIView animateWithDuration:1.0 animations:^{
//3D rotation C002
CABasicAnimation* animation2 = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
animation2.fromValue = #(0.5 * M_PI);
animation2.toValue = #(0 * M_PI);
animation2.repeatCount = 1;
animation2.duration = 1.0;
[self.C002.layer addAnimation:animation2 forKey:#"rotation"];
CATransform3D transform2 = CATransform3DIdentity;
transform2.m34 = 1.0 / 500.0;
self.C002.layer.transform = transform2;
} completion:^(BOOL finished) {
[self.C002.layer removeAnimationForKey:#"rotation"];
[self.C005.layer removeAnimationForKey:#"rotation"];
}];
}];
myFlag = YES;
}
}
Hope guys, you can help me.
When using the Coreanimation framework I can set an animation to repeat. I want to set a button to an "attract attention" mode which should make him grow and shrink by a small amount to get the users attention.
I already chained the grow and shrink animations via completion blocks. The question is if and how I can start the first animation from the second animation's completion block.
I do get the following warning which does make sense. What is an elegant solution to this problem? I'm not a fan of creating timers for stuff like this.
Capturing 'scaleAnimation' strongly in this block is likely to lead to
a retain cycle
- (void)attractAttention:(BOOL)flag{
_attractAttention = flag;
float resizeValue = 1.2f;
// Grow animation
POPSpringAnimation *scaleAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
scaleAnimation.fromValue = [NSValue valueWithCGSize:CGSizeMake(1.0f, 1.0f)];
scaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(resizeValue, resizeValue)];
scaleAnimation.completionBlock = ^(POPAnimation *anim, BOOL finished) {
// Grow animation done
POPSpringAnimation *scaleAnimationDown = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
scaleAnimationDown.fromValue = [NSValue valueWithCGSize:CGSizeMake(resizeValue, resizeValue)];
scaleAnimationDown.toValue = [NSValue valueWithCGSize:CGSizeMake(1.0f, 1.0f)];
scaleAnimationDown.completionBlock = ^(POPAnimation *anim, BOOL finished) {
// Shrink animation done
if (_attractAttention) {
[self.layer pop_addAnimation:scaleAnimation forKey:#"scaleUpAnimation"];
}
};
[self.layer pop_addAnimation:scaleAnimationDown forKey:#"scaleDownAnimation"];
};
[self.layer pop_addAnimation:scaleAnimation forKey:#"scaleUpAnimation"];
}
Edit:
I also tried to create a weak reference of the animation. This removes the error, but the animations do not work anymore:
__weak typeof(scaleAnimation) weakAnimation = scaleAnimation;
You can also use the properties autoreveres and repeatCount to achieve the same result without use blocks. It reduces the complexity:
POPSpringAnimation *scaleAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
scaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(0.5, 0.5)];
scaleAnimation.springBounciness = 0.f;
scaleAnimation.autoreverses = YES;
scaleAnimation.repeatCount=HUGE_VALF;
[layer pop_addAnimation:scaleAnimation forKey:#"scale"];
Even better, if you check this class https://github.com/facebook/pop/blob/master/pop/POPAnimation.h you will see there is a repeatForever property, so you could replace repeatCount by it:
scaleAnimation.repeatForever=YES;
Edited:
Do not use this solution. The POP framework has been updated with a “repeat” flag. It was not available when I first encountered this problem.
I solved it by a very easy work around:
- (void)attractAttention:(BOOL)flag{
_attractAttention = flag;
if (_attractAttention){
[self animatePop];
}
}
- (void)animatePop{
float resizeValue = 1.2f;
// Grow animation
POPSpringAnimation *scaleAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
scaleAnimation.fromValue = [NSValue valueWithCGSize:CGSizeMake(1.0f, 1.0f)];
scaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(resizeValue, resizeValue)];
scaleAnimation.completionBlock = ^(POPAnimation *anim, BOOL finished) {
// Grow animation done
POPSpringAnimation *scaleAnimationDown = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
scaleAnimationDown.fromValue = [NSValue valueWithCGSize:CGSizeMake(resizeValue, resizeValue)];
scaleAnimationDown.toValue = [NSValue valueWithCGSize:CGSizeMake(1.0f, 1.0f)];
scaleAnimationDown.completionBlock = ^(POPAnimation *anim, BOOL finished) {
// Shrink animation done
if (_attractAttention) {
[self animatePop];
}
};
[self.layer pop_addAnimation:scaleAnimationDown forKey:#"scaleDownAnimation"];
};
[self.layer pop_addAnimation:scaleAnimation forKey:#"scaleUpAnimation"];
}
Don't refer CABasicAnimation returns to the original position before the next animation and Objective-C - CABasicAnimation applying changes after animation? and CABasicAnimation rotate returns to original position
i have tried.
below code does,bottom->top->goes left->back to it’s original position.
bottom->top->goes left->back to its original position.
I need bottom->top->goes left.bottom->top->goes left so on…
-(void)addUpDownAnimationForButton:(UILabel*)label
{
CABasicAnimation * bottomAnimation ;
bottomAnimation =[CABasicAnimation animationWithKeyPath:#"transform.translation.y"];
[bottomAnimation setValue:#"animation1" forKey:#"id"];
bottomAnimation.delegate = self;
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:)];
bottomAnimation.duration = 2.0;
bottomAnimation.fromValue = [NSNumber numberWithFloat:13];
bottomAnimation.toValue = [NSNumber numberWithFloat:-7];
bottomAnimation.repeatCount = 0;
bottomAnimation.fillMode = kCAFillModeForwards;
bottomAnimation.removedOnCompletion = NO;
[btnSpecialForListing.titleLabel.layer addAnimation:bottomAnimation forKey:#"transform.translation.y"];
}
- (void)animationDidStop:(CAAnimation *)theAnimation2 finished:(BOOL)flag
{
if([[theAnimation2 valueForKey:#"id"] isEqual:#"animation1"]) {
CABasicAnimation *moveAnimation;
moveAnimation=[CABasicAnimation animationWithKeyPath:#"transform.translation.x"];
moveAnimation.duration=3.5;
moveAnimation.repeatCount=0;
moveAnimation.autoreverses=NO;
moveAnimation.fromValue=[NSNumber numberWithFloat:0];
moveAnimation.toValue=[NSNumber numberWithFloat:-400];
moveAnimation.removedOnCompletion = NO;
moveAnimation.fillMode = kCAFillModeRemoved;
[btnSpecialForListing.titleLabel.layer addAnimation:moveAnimation forKey:#"transform.translation.x"];
}
}
Any help would be greatly appreciated.
Thanks !!
Have you tried this?
moveAnimation.fillMode = kCAFillModeForwards;
moveAnimation.removedOnCompletion = NO;
EDIT
As far as I can see you can also use animateWithDuration which has a completion block.
Example would be:
CGRect rect = btnSpecialForListing.titleLabel.frame;
[UIView animateWithDuration:2.0 animations:^{
rect.origin.y = -7;
btnSpecialForListing.titleLabel.frame = rect;
} completion:^(BOOL finished){
[UIView animateWithDuration:3.5 animation:^{
rect.origin.x = -400;
btnSpecialForListing.titleLabel.frame = rect;
}];
}];
Swift 4.2/5:
moveAnimation.fillMode = .forwards
moveAnimation.isRemovedOnCompletion = false
As far as I can see, Apple wants us to move away from CGAffineTransform animations and into animations using:
myView.layoutConstraint.constant = someNewValue;
[myView layoutIfNeeded];
for animations that involve a translation of a view.
It also seems we should be now using CABasicAnimation animations for scale and rotation (and sometimes opacity) because it animates the view's layer and not the view and in doing so, plays nicely with auto layout.
I used the following code to apply an opacity and scale animation that worked beautifully:
[UIView animateWithDuration:0.1f animations:^{
// first animation
self.myMeButton.alpha = 1;
self.myMeButton.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.2f animations:^{
// second animation
self.myButton.transform = CGAffineTransformMakeScale(1, 1);
}];
}];
Of course auto layout plays havoc with the scale animation and so I am trying to find an alternative way to do it. So far, I have come up with this:
[CATransaction begin]; {
[CATransaction setCompletionBlock:^{
// code for when animation completes
self.pickMeButton.alpha = 1;
CABasicAnimation *scaleDown = [CABasicAnimation animationWithKeyPath:#"scale"];
scaleDown.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.1, 1.1, 1)];
scaleDown.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1)];
scaleDown.duration = 0.1;
[self.myButton.layer addAnimation:scaleDown forKey:nil];
}];
// describe animations:
CABasicAnimation* scaleUp = [CABasicAnimation animationWithKeyPath:#"scale"];
scaleUp.autoreverses = NO;
scaleUp.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1)];
scaleUp.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.1, 1.1, 1)];
CABasicAnimation *fadeAnim = [CABasicAnimation animationWithKeyPath:#"opacity"];
fadeAnim.fromValue=[NSNumber numberWithDouble:0.0];
fadeAnim.toValue=[NSNumber numberWithDouble:1.0];
// Customization for all animations:
CAAnimationGroup *group = [CAAnimationGroup animation];
group.duration = 0.2f;
group.repeatCount = 1;
group.autoreverses = NO;
group.animations = #[scaleUp, fadeAnim];
// add animations to the view's layer:
[self.myButton.layer addAnimation:group forKey:#"allMyAnimations"];
} [CATransaction commit];
As you can see the code almost 3 times as long as before and the animation on the device is noticeably less 'smooth' than it was previously.
Is there any way to do this better?
Thanks in advance for your response.
EDIT: This seems to have done the trick in that the animations are smooth, but I still feel like the code for this could be a lot more succinct.
[UIView animateWithDuration:0.2f animations:^{
self.pickMeButton.alpha = 1;
CABasicAnimation* scaleUp = [CABasicAnimation animationWithKeyPath:#"transform"];
scaleUp.duration = 0.2;
scaleUp.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.1, 0.1, 1)];
scaleUp.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.2, 1.2, 1)];
[self.pickMeButton.layer addAnimation:scaleUp forKey:nil];
}completion:^(BOOL finished) {
CABasicAnimation* scaleDown = [CABasicAnimation animationWithKeyPath:#"transform"];
scaleDown.duration = 0.1;
scaleDown.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.2, 1.2, 1)];
scaleDown.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1)];
[self.pickMeButton.layer addAnimation:scaleDown forKey:nil];
}];
I don't know why you want to do it with CABasicAnimation for scale. You can do it like you mention at the top of your question. Set a new value for the view's width and height constraint constant values and then use [myView layoutIfNeeded] inside animateWithDuration. If the view doesn't have height and width constraints, but has constants to the top and bottom and/or left and right edges of the superview, change those values instead.
im looking to Rotate a UIImageView, and i found some helpfull code
- (void) runSpinAnimationOnView:(UIView*)view duration:(CGFloat)duration rotations: (CGFloat)rotations repeat:(float)repeat;
{
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2.0 /* full rotation*/ * rotations * duration ];
rotationAnimation.duration = duration;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = repeat;
[view.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
}
The issue is that the image flips back when the animation finishes, im looking to rotate it and keep it there. Would i need to flip the actual image or can i flip the ImageView. And how would i do that`?
Why not use a standard UIView animation?
If you really need the repeatCount, then I suggest you use the old style:
[UIView beginAnimations]
[UIView setAnimationRepeatCount:repeatCount];
[UIView setAnimationDuration:duration];
yourView.transform = CGAffineTransformMakeRotation(degrees * M_PI);
[UIView commitAnimations];
Otherwise, you can use the much preferred new style:
[UIView animateWithDuration:duration animations:^{
yourView.transform = CGAffineTransformMakeRotation(angle);
} completion:^(BOOL finished) {
// your completion code here
}]
If you need other options like autoreverse, allow user interaction or repeating use [UIView animateWithDuration:delay:options:animations:completion:];
I prefer to cover all my bases with CoreAnimation. Set the final value before the animation, set the fill mode, and set do not remove.
- (void) runSpinAnimationOnView:(UIView*)view duration:(CGFloat)duration rotations: (CGFloat)rotations repeat:(float)repeat;
{
NSString * animationKeyPath = #"transform.rotation.z";
CGFloat finalRotation = M_PI * 2.0f * rotations;
[view.layer setValue:#(finalRotation) forKey:animationKeyPath];
CABasicAnimation* rotationAnimation = [CABasicAnimation animationWithKeyPath:animationKeyPath];
rotationAnimation.fromValue = #0.0f;
rotationAnimation.toValue = #(finalRotation);
rotationAnimation.fillMode = kCAFillModeBoth;
rotationAnimation.removedOnCompletion = NO;
rotationAnimation.duration = duration;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = repeat;
[view.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
}