CALayer animate contents between more than 2 images impossible? - ios

Does anyone has managed to animate the contents property of a CALayer between 3 images with successful interpolation. It seems that the animation is unable to manage interpolation between more than two images. My CGImages have all the same size.
CAKeyframeAnimation * animation =
[CAKeyframeAnimation animationWithKeyPath:#"contents"];
animation.calculationMode = kCAAnimationLinear;
animation.beginTime = AVCoreAnimationBeginTimeAtZero;
animation.duration = duration;
animation.fillMode = kCAFillModeBoth;
animation.removedOnCompletion = NO;
CGFloat width1 = CGImageGetWidth(image1.CGImage);
CGFloat height1 = CGImageGetHeight(image1.CGImage);
CGFloat width2 = CGImageGetWidth(image2.CGImage);
CGFloat height2 = CGImageGetHeight(image2.CGImage);
CGFloat width3 = CGImageGetWidth(image3.CGImage);
CGFloat height3 = CGImageGetHeight(image3.CGImage);
NSLog(#"first: W:%f H:%f, second: W:%f H:%f, third: W:%f H:%f",width1,height1,width2,height2,width3,height3);
animation.keyTimes = #[#0.0,#0.1,#0.3,#0.6,#1.0];
animation.values =
#[(id)image1.CGImage,
(id)image1.CGImage,
(id)image2.CGImage,
(id)image2.CGImage,
(id)image3.CGImage];
THE Log
2014-01-22 16:05:24.985 myApp[8527:60b] first: W:2592.000000 H:1936.000000, second: W:2592.000000 H:1936.000000, third: W:2592.000000 H:1936.000000

Related

Glitchy animation CAAnimation

I am trying to create a new imageview which is going to have an animation feature. But, I have a problem about my animation, lets see;
Here, all I need is to make this animation seems continuous. I mean, without that glitch at the beginning of every loop. Just right edge of the corner at top left.
Here are my animations;
let strokeEndAnimation: CAAnimation = {
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 2
let group = CAAnimationGroup()
group.duration = 2.3
group.repeatCount = .infinity
group.animations = [animation]
return group
}()
let strokeStartAnimation: CAAnimation = {
let animation = CABasicAnimation(keyPath: "strokeStart")
animation.beginTime = 0.3
animation.fromValue = 0
animation.toValue = 1
animation.duration = 2
let group = CAAnimationGroup()
group.duration = 2.3
group.repeatCount = .infinity
group.animations = [animation]
return group
}()
You can't simply achieve it with a standard closed path and strokeStart + strokeEnd, because these value are Float lies between 0.0 and 1.0, however you can play some tricks on the path itself.
I'll take a circular path for example since it's more straight-forward.
// Create an arc path with 2.353π (animation start at 0.15)
let arcPath = UIBezierPath(arcCenter: CGPoint(x: 100, y: 100), radius: 90.0, startAngle: 0, endAngle: CGFloat(.pi * 2.353), clockwise: true)
let pathLayer = CAShapeLayer()
pathLayer.path = arcPath.cgPath
pathLayer.strokeColor = UIColor.black.cgColor
pathLayer.fillColor = nil
pathLayer.lineWidth = 10.0
// stroke start run from 0 - 0.85 (0π - 2π)
let aniSS = CABasicAnimation(keyPath: "strokeStart")
aniSS.fromValue = 0
aniSS.toValue = 0.85
aniSS.duration = 2
// stroke end run from 0.15 - 1 (0.353π - 2.353π)
let aniSE = CABasicAnimation(keyPath: "strokeEnd")
aniSE.fromValue = 0.15
aniSE.toValue = 1
aniSE.duration = 2
let group = CAAnimationGroup()
group.duration = 2
group.repeatCount = .infinity
group.animations = [aniSS, aniSE]
pathLayer.add(group, forKey: "strokeAnimation")
So that when an animation loop end, the stroke sit at 2π - 2.353π which looks exactly identical as when an animation loop start: 0π - 0.353π, might not be the optimized solution but it did the work.
I know this post is 5 years old, but there still aren't any good solutions out there to solve this issue. I was able to achieve the desired result using a second layer. The main progress layer is in blue. The second layer is in red to show how it replaces the first layer, and makes it appear to be the same loader:
Here's the code:
- (void)animate {
[self beginning];
[self end];
}
- (void)beginning {
[_progressLayer removeAllAnimations];
CAKeyframeAnimation *animationStart = [CAKeyframeAnimation animationWithKeyPath:kStrokeStart];
animationStart.values = #[#0, #0, #0.75];
animationStart.keyTimes = #[#0, #0.25, #1];
animationStart.duration = 2;
CAKeyframeAnimation *animationEnd = [CAKeyframeAnimation animationWithKeyPath:kStrokeEnd];
animationEnd.values = #[#0, #0.25, #1];
animationEnd.keyTimes = #[#0, #0.25, #1];
animationEnd.duration = 2;
CAAnimationGroup *group = [CAAnimationGroup new];
group.duration = 2;
group.repeatCount = INFINITY;
group.animations = #[animationStart, animationEnd];
[_progressLayer addAnimation:group forKey:#"beginning"];
}
- (void)end {
[_progressLayerTail removeAllAnimations];
CABasicAnimation *animationStart = [CABasicAnimation animationWithKeyPath:kStrokeStart];
animationStart.fromValue = #0.75;
animationStart.toValue = #1;
animationStart.duration = 0.5;
CABasicAnimation *animationEnd = [CABasicAnimation animationWithKeyPath:kStrokeEnd];
animationEnd.fromValue = #1;
animationEnd.toValue = #1;
animationEnd.duration = 0.5;
CAAnimationGroup *group = [CAAnimationGroup new];
group.duration = 2;
group.repeatCount = INFINITY;
group.animations = #[animationStart, animationEnd];
[_progressLayerTail addAnimation:group forKey:#"end"];
}

CABasicAnimation lags (iOS5)

i got some simple animation that works perfect on iOS 6, but on iOS5 its lagging and often freezes screen without any reason
i need help really, because i dont have any ideas what could be wrong here. By the way i tried to turn off each of the animations, and looks like the problem is in "transform" animation
here is the code:
-(void)drawDotAtPointAnimated:(CGPoint)p withRadius:(CGFloat)radius andColor:(UIColor *)color
{
CGRect circleFrame = CGRectMake(p.x - radius*self.scale, p.y - radius*self.scale, radius*2*self.scale, radius*2*self.scale);
CGPoint circleAnchor = CGPointMake(0.5, 0.5);
NSLog(#"animating dot with x = %f and y = %f", p.x, p.y);
NSLog(#"=====mid x = %f, mid y = %f", CGRectGetMidX(circleFrame), CGRectGetMidY(circleFrame));
NSLog(#"=====max x = %f, max y = %f", CGRectGetMaxX(circleFrame), CGRectGetMaxY(circleFrame));
self.shapeLayer = [CAShapeLayer layer];
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, circleFrame.size.width, circleFrame.size.height)];
self.shapeLayer.path = path.CGPath;
self.shapeLayer.anchorPoint = circleAnchor;
self.shapeLayer.frame = circleFrame;
self.shapeLayer.fillColor = color.CGColor;
[self.layer addSublayer:self.shapeLayer];
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"transform"];
animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.0, 0.0, 0)];
animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(4.0, 4.0, 1.0)];
if(self.mode == GameModeOffline) {
animation.repeatCount = OFFLINE_GAME_DOT_ANIMATION_REPEAT_COUNT;
} else {
animation.repeatCount = ONLINE_GAME_DOT_ANIMATION_REPEAT_COUNT;
}
animation.duration = 1;
animation.delegate = self;
[self.shapeLayer addAnimation:animation forKey:#"transform"];
CABasicAnimation* fadeAnim = [CABasicAnimation animationWithKeyPath:#"opacity"];
fadeAnim.fromValue = [NSNumber numberWithFloat:1.0];
fadeAnim.toValue = [NSNumber numberWithFloat:0.0];
fadeAnim.duration = 1.0;
if(self.mode == GameModeOffline) {
fadeAnim.repeatCount = OFFLINE_GAME_DOT_ANIMATION_REPEAT_COUNT;
} else {
fadeAnim.repeatCount = ONLINE_GAME_DOT_ANIMATION_REPEAT_COUNT;
}
[self.shapeLayer addAnimation:fadeAnim forKey:#"opacity"];
self.shapeLayer.transform = CATransform3DMakeScale(4.0, 4.0, 1);
self.shapeLayer.opacity = 0.0;
any help would be appreciated
Scale by zero at start? Don't you want to scale by {1, 1, 1} ? (ie. identity transform)
(It seems this has simply solved the problem, so I'm posting my comment as an answer).

How to animate an image in backward using CAKeyFrameAnimation

I'm new in iOS development. I'm creating a Keyframe animation for rotating the image.The image was successfully rotated in forward.I want to rotate the image in full and full backward.
This is my code.
UIImageView *tempView = [[UIImageView alloc] initWithFrame:CGRectMake(28,158,133,133)];
tempView.image = [UIImage imageNamed:#"1_03.png"];
[self.view addSubview:tempView];
const NSUInteger rotations = 1;
const NSTimeInterval duration = 4.0f;
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"transform.rotation"];
CGFloat touchUpStartAngle = 0;
CGFloat touchUpEndAngle = (M_PI);
CGFloat angularVelocity = (((2 * M_PI) * rotations) + M_PI) / duration;
anim.values = #[#(touchUpStartAngle), #(touchUpStartAngle + angularVelocity * duration)];
anim.duration = duration;
anim.delegate = self;
anim.repeatCount = INFINITY;
[tempView.layer addAnimation:anim forKey:#"animation"];
tempView.transform = CGAffineTransformMakeRotation(touchUpStartAngle + (touchUpEndAngle));
How to do this.
Try this.
UIImageView *tempView = [[UIImageView alloc] initWithFrame:CGRectMake(28,158,133,133)];
tempView.image = [UIImage imageNamed:#"1_03.png"];
[self.view addSubview:tempView];
const NSUInteger rotations = 1;
const NSTimeInterval duration = 4.0f;
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"transform.rotation"];
CGFloat touchUpStartAngle = 0;
CGFloat touchUpEndAngle = (M_PI);
CGFloat angularVelocity = (((2 * M_PI) * rotations) + M_PI) / duration;
anim.values = #[#(touchUpStartAngle), #(touchUpStartAngle - angularVelocity * duration)];
anim.duration = duration;
anim.delegate = self;
anim.repeatCount = INFINITY;
[tempView.layer addAnimation:anim forKey:#"animation"];
tempView.transform = CGAffineTransformMakeRotation(touchUpStartAngle + (touchUpEndAngle));
Try this
CGFloat touchUpStartAngle = 0;
CGFloat touchUpEndAngle = (M_PI);
CGFloat angularVelocity = (((2 * M_PI) * rotations) + M_PI) / duration;
anim.values = #[#(touchUpStartAngle), #(touchUpStartAngle - angularVelocity * duration)];

Animating a UIView to a value without a specified duration

I want to animate a sub-classed UIView using Core Animation. The oddity in my case is that I want the animation to run a fixed amount every frame and not by the duration. So animating from 100 -> 200 should take longer than 100 -> 50, but the "velocity" of the view should be constant. My code currently looks like this:
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"transform"];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
animation.duration = 0.4;
[[self layer] addAnimation:animation forKey:#"transform"];
self.transform = CGAffineTransformMakeTranslation(0, 100);
It this possible? How would it be done?
Why not just compute the desired time from the desired movement? Something like:
CGFloat distanceToTimeFactor = 0.1;
CGPoint current = self.center;
CGPoint new = CGPointMake(100,100);
CGFloat xDist = (new.x - current.x);
CGFloat yDist = (new.y - current.y);
CGFloat distance = sqrt((xDist * xDist) + (yDist * yDist));
CGFloat duration = distance * distanceToTimeFactor;
And then animate with that duration.

How do you create a wiggle animation similar to iphone deletion animation

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.

Resources