CAKeyframeAnimation repeat after a delay - iphone

I have 3 uiviews which have a CAKeyframeAnimation layer added to them to bounce. Currently i add these to each uiview after a delay and set them to repeat.
What i need them to do is repeat but after a delay. so view 1 animates, but waits till after view 2 and 3 have animated before starting again.
I can't seem to read anywhere in the docs that mention repeating a CAKeyframeAnimation with a repeat start time delay.
Can anyone think of an idea to get the delays between the views?
Thanks
Dan
Code so far:
- (CAKeyframeAnimation *)jumpAnimation
{
// these three values are subject to experimentation
CGFloat initialMomentum = 80.0f; // positive is upwards, per sec
CGFloat gravityConstant = 250.0f; // downwards pull per sec
CGFloat dampeningFactorPerBounce = 0.6; // percent of rebound
// internal values for the calculation
CGFloat momentum = initialMomentum; // momentum starts with initial value
CGFloat positionOffset = 0; // we begin at the original position
CGFloat slicesPerSecond = 60.0f; // how many values per second to calculate
CGFloat lowerMomentumCutoff = 5.0f; // below this upward momentum animation ends
CGFloat duration = 0;
NSMutableArray *values = [NSMutableArray array];
do
{
duration += 1.0f/slicesPerSecond;
positionOffset+=momentum/slicesPerSecond;
if (positionOffset<0)
{
positionOffset=0;
momentum=-momentum*dampeningFactorPerBounce;
}
// gravity pulls the momentum down
momentum -= gravityConstant/slicesPerSecond;
CATransform3D transform = CATransform3DMakeTranslation(0, -positionOffset, 0);
[values addObject:[NSValue valueWithCATransform3D:transform]];
} while (!(positionOffset==0 && momentum < lowerMomentumCutoff));
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
animation.duration = duration;
animation.fillMode = kCAFillModeForwards;
animation.values = values;
animation.repeatCount = HUGE_VAL;
animation.delegate =self;
animation.autoreverses = YES;
return animation;
}

Related

CABasicAnimation maintain a constant speed

I am trying to create a simple animation with CABasicAnimation that will always hold a constant speed, even if the distance the animation must travel is never the same. Here is my code so far, it is scrolling a label that will always vary in size, but cannot yet hold a constant speed. Help is much appreciated.
CABasicAnimation *theAnimation;
theAnimation = [CABasicAnimation animationWithKeyPath:#"transform.translation.x"];
theAnimation.speed = 1.0f;
NSNumber *fromValue = [NSNumber numberWithFloat:self.mainLabel.frame.origin.x];
NSNumber *toValue = [NSNumber numberWithFloat:-self.mainLabel.frame.size.width - self.view.frame.size.height];
theAnimation.fromValue = fromValue;
theAnimation.toValue = toValue;
//theAnimation.duration = toValue.floatValue - fromValue.floatValue;//Not right.
theAnimation.repeatCount = 999;
theAnimation.autoreverses = NO;
[mainLabel.layer addAnimation:theAnimation forKey:#"animateLayer"];
Did you try add:
theAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
in example after
theAnimation.speed = 1.0f;
?
This function set linear sped of animation.

Animation of a UIImage moving from one UIImageView to another

I've managed to get along using [UIView animateWithDuration... to get animations done that I need in my UI. Now I want to move an image along a curved path, and that whole CAAnimation cluster looks pretty daunting to me.
I'd be much obliged if someone could help me fill in the method I wish I could code, which would look like this:
- (void)makeAnImageFlyFrom:(UIImageView *)imageViewA to:(UIImageView *)imageViewB alongPath:(CGMutablePathRef)path duration:(NSTimeInterval)duration {
UIImage *imageToFly = imageViewA.image;
// magic, that i'm too lazy to learn right now goes here
// image flys along the path and gets scaled to match imageViewB.
// then view/layer hierarchy is just as it was, but imageViewB has a new image
// maybe this happens on animationDidStop...
imageViewB.image = imageToFly;
}
Feel free to replace params (like path ref) if you think there's a smarter interface for this kind of method. Thanks in advance.
Ok, a whole Sunday later, here's a method that I think works. I'm still unclear about a few things, noted in comments, but if you need this type of thing, feel free to cut and paste. I promise not call you lazy:
- (void)makeAnImageFlyFrom:(UIImageView *)imageViewA to:(UIImageView *)imageViewB duration:(NSTimeInterval)duration {
// it's simpler but less general to not pass in the path. i chose simpler because
// there's a lot of geometry work using the imageView frames here anyway.
UIImageView *animationView = [[UIImageView alloc] initWithImage:imageViewA.image];
animationView.tag = kANIMATION_IMAGE_TAG;
animationView.frame = imageViewA.frame;
[self addSubview:animationView];
// scale
CABasicAnimation *resizeAnimation = [CABasicAnimation animationWithKeyPath:#"bounds.size"];
[resizeAnimation setFromValue:[NSValue valueWithCGSize:imageViewA.bounds.size]];
[resizeAnimation setToValue:[NSValue valueWithCGSize:imageViewB.bounds.size]];
// build the path
CGRect aRect = [imageViewA convertRect:imageViewA.bounds toView:self];
CGRect bRect = [imageViewB convertRect:imageViewB.bounds toView:self];
// unclear why i'm doing this, but the rects converted to this view's
// coordinate system seemed have origin's offset negatively by half their size
CGFloat startX = aRect.origin.x + aRect.size.width / 2.0;
CGFloat startY = aRect.origin.y + aRect.size.height / 2.0;
CGFloat endX = bRect.origin.x + bRect.size.width / 2.0;
CGFloat endY = bRect.origin.y + bRect.size.height / 2.0;
CGFloat deltaX = endX - startX;
CGFloat deltaY = endY - startY;
// these control points suited the path i needed. your results may vary
CGFloat cp0X = startX + 0.3*deltaX;
CGFloat cp0Y = startY - 1.3*deltaY;
CGFloat cp1X = endX + 0.1*deltaX;
CGFloat cp1Y = endY - 0.5*deltaY;
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, startX, startY);
CGPathAddCurveToPoint(path, NULL, cp0X, cp0Y, cp1X, cp1Y, endX, endY);
// keyframe animation
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
keyframeAnimation.calculationMode = kCAAnimationPaced;
keyframeAnimation.fillMode = kCAFillModeForwards;
keyframeAnimation.removedOnCompletion = NO;
keyframeAnimation.path = path;
// assuming i need to manually release, despite ARC, but not sure
CGPathRelease(path);
// a little unclear about the fillMode, but it works
// also unclear about removeOnCompletion, because I remove the animationView
// but that seems to be insufficient
CAAnimationGroup *group = [CAAnimationGroup animation];
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
[group setAnimations:[NSArray arrayWithObjects:keyframeAnimation, resizeAnimation, nil]];
group.duration = duration;
group.delegate = self;
// unclear about what i'm naming with the keys here, and why
[group setValue:animationView forKey:#"animationView"];
[animationView.layer addAnimation:group forKey:#"animationGroup"];
}
// clean up after like this
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
UIImageView *imageViewForAnimation = (UIImageView *)[self viewWithTag:kANIMATION_IMAGE_TAG];
// get the imageView passed to the animation as the destination
UIImageView *imageViewB = (UIImageView *)[self viewWithTag:kDEST_TAG];
imageViewB.image = imageViewForAnimation.image;
[imageViewForAnimation removeFromSuperview];
}

How can I add an image to the back of a CALayer after rotate 90 degrees?

I want to add a back image to a CALayer when the my rotation transform degree is higher than 90.
It is just like a Flipboard flip animation.
This is my current code:
CALayer *layer = self.view.layer;
int frames = 130;
layer.anchorPoint = CGPointMake(0, 0.5);
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0 / -2000.0;
transform = CATransform3DRotate(transform, -frames * M_PI / 180.0, 0.0, 1.0, 0.0);
self.view.layer.transform = transform;
CAKeyframeAnimation *pivot = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
NSMutableArray *values = [[NSMutableArray alloc] initWithCapacity:45];
NSMutableArray *times = [[NSMutableArray alloc] initWithCapacity:45];
for (int i = 0; i < frames; i++) {
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0 / -2000.0;
transform = CATransform3DRotate(transform, -M_PI / 180.0 * i, 0.0, 1.0, 0.0);
[values addObject:[NSValue valueWithCATransform3D:transform]];
[times addObject:[NSNumber numberWithFloat:(float)i / (frames - 1)]];
}
pivot.values = values;
pivot.keyTimes = times;
[values release];
[times release];
pivot.duration = 0.5f;
pivot.calculationMode = kCAAnimationLinear;
[layer addAnimation: pivot forKey: #"pivot"];
Could anyone tell me how to add a back image just like the flip effect.
As far as I know, the easiest way to do it is to add two sublayers representing the front side and the back side of the page. Set both the sublayers' doubleSided property to NO. Set the back side layer transform to the same flipped transform you're animating to. Then, when the page's front side faces the viewer, the front side layer is visible and the back side layer is "hidden", and vice versa.
See GeekGameBoard source code (Card.m) for a sample of the technique.

iPhone SDK : Create Wiggle Effect [duplicate]

We are currently developing an application that contains a series of icons. We want the icons to wiggle like the app deletion animations when pressed. What would be the best way to code this animation sequence?
The answer by Vinzius is very cool. However the wobble only rotates from 0 Radians to 0.08. Thus the wobble can look a little unbalanced. If you get this same issue then you may want to add both a negative and a positive rotation by using a CAKeyframeAnimation rather than a CABasicRotation:
- (CAAnimation*)getShakeAnimation
{
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.autoreverses = YES;
animation.duration = 0.125;
animation.repeatCount = HUGE_VALF;
return animation;
}
You can use this animation method for your view or button like this.
[self.yourbutton.layer addAnimation:[self getShakeAnimation] forKey:#""];
SWIFT :-
let transformAnim = CAKeyframeAnimation(keyPath:"transform")
transformAnim.values = [NSValue(CATransform3D: CATransform3DMakeRotation(0.04, 0.0, 0.0, 1.0)),NSValue(CATransform3D: CATransform3DMakeRotation(-0.04 , 0, 0, 1))]
transformAnim.autoreverses = true
transformAnim.duration = (Double(indexPath.row)%2) == 0 ? 0.115 : 0.105
transformAnim.repeatCount = Float.infinity
self.layer.addAnimation(transformAnim, forKey: "transform")
Objective C :-
-(CAKeyframeAnimation *)wiggleView
{
CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
CGFloat wobbleAngle = 0.04f;
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.autoreverses = YES;
animation.duration = 0.125;
animation.repeatCount = HUGE_VALF;
return animation;
}
Looking at the iOS implementation a bit closer, there are two things that make theirs a bit more realistic than the code mentioned here:
The icons appear to have a bounce as well as a rotation
Every icon has its own timing -- they are not all synchronized
I based myself on the answers here (and with some help from this answer) to add the rotation, the bounce and a bit of randomness to the duration of each animation.
#define kWiggleBounceY 4.0f
#define kWiggleBounceDuration 0.12
#define kWiggleBounceDurationVariance 0.025
#define kWiggleRotateAngle 0.06f
#define kWiggleRotateDuration 0.1
#define kWiggleRotateDurationVariance 0.025
-(void)startWiggling {
[UIView animateWithDuration:0
animations:^{
[self.layer addAnimation:[self rotationAnimation] forKey:#"rotation"];
[self.layer addAnimation:[self bounceAnimation] forKey:#"bounce"];
self.transform = CGAffineTransformIdentity;
}];
}
-(CAAnimation*)rotationAnimation {
CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.values = #[#(-kWiggleRotateAngle), #(kWiggleRotateAngle)];
animation.autoreverses = YES;
animation.duration = [self randomizeInterval:kWiggleRotateDuration
withVariance:kWiggleRotateDurationVariance];
animation.repeatCount = HUGE_VALF;
return animation;
}
-(CAAnimation*)bounceAnimation {
CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.translation.y"];
animation.values = #[#(kWiggleBounceY), #(0.0)];
animation.autoreverses = YES;
animation.duration = [self randomizeInterval:kWiggleBounceDuration
withVariance:kWiggleBounceDurationVariance];
animation.repeatCount = HUGE_VALF;
return animation;
}
-(NSTimeInterval)randomizeInterval:(NSTimeInterval)interval withVariance:(double)variance {
double random = (arc4random_uniform(1000) - 500.0) / 500.0;
return interval + variance * random;
}
I implemented this code on a UICollectionView which had 30 items bouncing and the performance was flawless on an iPad 2.
I tried to do something like that for an iPad app.
I tried to do some rotations (with CAAnimation) to the view. Here is a sample code I wrote :
- (CAAnimation*)getShakeAnimation {
CABasicAnimation *animation;
CATransform3D transform;
// Create the rotation matrix
transform = CATransform3DMakeRotation(0.08, 0, 0, 1.0);
// Create a basic animation to animate the layer's transform
animation = [CABasicAnimation animationWithKeyPath:#"transform"];
// Assign the transform as the animation's value
animation.toValue = [NSValue valueWithCATransform3D:transform];
animation.autoreverses = YES;
animation.duration = 0.1;
animation.repeatCount = HUGE_VALF;
return animation;
}
And you should try to apply this one to your layer (with function : addAnimation).
Here, autoreverses property is to alternate left and right orientation.
Try setting others values to the angle and duration.
But in my case I had to add others angles to the CATransform3DMakeRotation method, depending on the initial layer orientation ^^
Good Luck !
Vincent
Rewrote Sebastien's answer in Swift 3.
let wiggleBounceY = 4.0
let wiggleBounceDuration = 0.12
let wiggleBounceDurationVariance = 0.025
let wiggleRotateAngle = 0.06
let wiggleRotateDuration = 0.10
let wiggleRotateDurationVariance = 0.025
func randomize(interval: TimeInterval, withVariance variance: Double) -> Double{
let random = (Double(arc4random_uniform(1000)) - 500.0) / 500.0
return interval + variance * random
}
func startWiggle(for view: UIView){
//Create rotation animation
let rotationAnim = CAKeyframeAnimation(keyPath: "transform.rotation.z")
rotationAnim.values = [-wiggleRotateAngle, wiggleRotateAngle]
rotationAnim.autoreverses = true
rotationAnim.duration = randomize(interval: wiggleRotateDuration, withVariance: wiggleRotateDurationVariance)
rotationAnim.repeatCount = HUGE
//Create bounce animation
let bounceAnimation = CAKeyframeAnimation(keyPath: "transform.translation.y")
bounceAnimation.values = [wiggleBounceY, 0]
bounceAnimation.autoreverses = true
bounceAnimation.duration = randomize(interval: wiggleBounceDuration, withVariance: wiggleBounceDurationVariance)
bounceAnimation.repeatCount = HUGE
//Apply animations to view
UIView.animate(withDuration: 0) {
view.layer.add(rotationAnim, forKey: "rotation")
view.layer.add(bounceAnimation, forKey: "bounce")
view.transform = .identity
}
}
func stopWiggle(for view: UIView){
view.layer.removeAllAnimations()
}
func startWiggling() {
deleteButton.isHidden = false
guard contentView.layer.animation(forKey: "wiggle") == nil else { return }
guard contentView.layer.animation(forKey: "bounce") == nil else { return }
let angle = 0.04
let wiggle = CAKeyframeAnimation(keyPath: "transform.rotation.z")
wiggle.values = [-angle, angle]
wiggle.autoreverses = true
wiggle.duration = randomInterval(0.1, variance: 0.025)
wiggle.repeatCount = Float.infinity
contentView.layer.add(wiggle, forKey: "wiggle")
let bounce = CAKeyframeAnimation(keyPath: "transform.translation.y")
bounce.values = [4.0, 0.0]
bounce.autoreverses = true
bounce.duration = randomInterval(0.12, variance: 0.025)
bounce.repeatCount = Float.infinity
contentView.layer.add(bounce, forKey: "bounce")
}
func stopWiggling() {
deleteButton.isHidden = true
contentView.layer.removeAllAnimations()
}
func randomInterval(_ interval: TimeInterval, variance: Double) -> TimeInterval {
return interval + variance * Double((Double(arc4random_uniform(1000)) - 500.0) / 500.0)
}
Look at this iOS SpingBoard example
Answered in another thread a Swift 4 version of what is apparently Apple's own algorithm reverse engineered:
https://stackoverflow.com/a/47730519/5018607
Edited paiego's code to fit my needs: visual error animation feedback upon user's action (tap). It happens once - it's not a constant wiggling like SpringBoard app edit wiggle animation.
- (CAAnimation *)shakeAnimation {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
CGFloat wobbleAngle = 0.06f;
NSValue *valLeft;
NSValue *valRight;
NSMutableArray *values = [NSMutableArray new];
for (int i = 0; i < 5; i++) {
valLeft = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(wobbleAngle, 0.0f, 0.0f, 1.0f)];
valRight = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(-wobbleAngle, 0.0f, 0.0f, 1.0f)];
[values addObjectsFromArray:#[valLeft, valRight]];
wobbleAngle*=0.66;
}
animation.values = [values copy];
animation.duration = 0.7;
return animation;
}
Usage:
[your_view.layer addAnimation:[self shakeAnimation] forKey:#""]; //do the shake animation
your_view.transform = CGAffineTransformIdentity; //return the view back to original
Hope this helps someone else.

smooth pendular animation with Core Animation Framework

im trying to do an animation with an uiimageview.
in this view is an image with an arrow, that i want to rotate about 45 degrees back and forward very smoothly like an pendular or an old clock.
something like this, just smooth and with my image: http://bit.ly/cArvNw (found this with google ;) )
my current setup looks like this:
UIImageView* rotatingImage = [[UIImageView alloc] initWithFrame:CGRectMake(10.0, 10.0, 200.0, 200.0)];
[rotatingImage setImage:[UIImage imageNamed:#"wind.png"]];
CALayer *layer = rotatingImage.layer;
CAKeyframeAnimation *animation;
animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.duration = 0.5f;
animation.cumulative = YES;
animation.repeatCount = 100;
animation.values = [NSArray arrayWithObjects: // i.e., Rotation values for the 3 keyframes, in RADIANS
[NSNumber numberWithFloat:[self degreesToRadians:45.0]],
[NSNumber numberWithFloat:[self degreesToRadians:-45.0]], nil];
animation.keyTimes = [NSArray arrayWithObjects: // Relative timing values for the 3 keyframes
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:.5], nil];
animation.timingFunctions = [NSArray arrayWithObjects:
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn], // from keyframe 1 to keyframe 2
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut], nil]; // from keyframe 2 to keyframe 3
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[layer addAnimation:animation forKey:nil];
[self addSubview:rotatingImage];
this is really a pain, add 45 degrees, then 45 degrees backward with "-45" doesnt work.
im very new to core animation and dont know how to setup my code, to get my wanted animation.
can anybody help please?
Have you looked at Apple's Example iPhone Application, "Metronome"?
It does almost exactly what you're trying to do, using Core Animation.
Note: This method does not use Core Animation, but it's very simple and probably uses less resources.
First, make the UIImageView twice as tall, thereby making the center of rotation equal to the center of the image.
Then, define these in the #interface of the header file (of, say, your UIViewController):
BOOL goingCW; // going clockwise = YES, going counterclockwise = NO
CGFloat angle; // angle by which to change the image's rotation value
Then put this in an init method that runs once:
goingCW = YES; // change this to NO to make it start rotating CCW instead
angle = 0;
[NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:#selector(update) userInfo:nil repeats:YES];
Then define update, given that arrow is the UIImageView instance:
- (void)update {
if (goingCW) {
if (angle > M_PI / 4) goingCW = NO; // M_PI / 4 is equal to 45 degrees
angle += M_PI / 60; // increase 60 to slow down the rotation, etc.
} else {
if (angle < -M_PI / 4) goingCW = YES; // -M_PI / 4 is equal to -45 degrees
angle -= M_PI / 60; // increase 60 to slow down the rotation, etc.
}
// rotate the UIImageView appropriately
arrow.transform = CGAffineTransformMakeRotation(angle);
}
This also assumes that you are starting in the facing-down position.