Glitchy animation CAAnimation - ios

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"];
}

Related

CAShapeLayer animation doesn't stay on screen but disappears

I'm trying to draw a animated circle but every segment needs to have another color. Now everything works except that my piece that is just drawed before I call the method again disappears so only the last part stays. I don't want that, I want that after 4 times a whole circle is drawn in different color strokes.
How can I fix this that it doesn't disappears?
This is my init code:
- (void)_initCircle {
_circle = [CAShapeLayer layer];
_radius = 100;
// Make a circular shape
_circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0 * _radius, 2.0 * _radius) cornerRadius:_radius].CGPath;
// Center the shape in self.view
_circle.position = CGPointMake(CGRectGetMidX(self.view.frame) - _radius,
CGRectGetMidY(self.view.frame) - _radius);
_circle.fillColor = [UIColor clearColor].CGColor;
_circle.lineWidth = 5;
// Add to parent layer
[self.view.layer addSublayer:_circle];
}
This is my draw piece:
- (void)_drawStrokeOnCircleFrom:(float)start to:(float)end withColor:(UIColor *)color {
// Configure the apperence of the circle
_circle.strokeColor = color.CGColor;
_circle.strokeStart = start;
_circle.strokeEnd = end;
[CATransaction begin];
// Configure animation
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 2.0; // "animate over 10 seconds or so.."
drawAnimation.repeatCount = 1.0; // Animate only once..
// Animate from no part of the stroke being drawn to the entire stroke being drawn
drawAnimation.fromValue = [NSNumber numberWithFloat:start];
drawAnimation.toValue = [NSNumber numberWithFloat:end];
// Experiment with timing to get the appearence to look the way you want
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[CATransaction setCompletionBlock:^{
NSLog(#"Complete animation");
_index++;
if(_index < _votes.count){
_startStroke = _endStroke;
_endStroke += [_votes[_index] floatValue] / _totalListens;
[self _drawStrokeOnCircleFrom:_startStroke to:_endStroke withColor:_colors[_index]];
}
}];
// Add the animation to the circle
[_circle addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
[CATransaction commit];
}
Try setting these values on your CABasicAnimation:
drawAnimation.removedOnCompletion = NO;
drawAnimation.fillMode = kCAFillModeForwards;
That worked for me using "kCAFillModeBoth", both statements are necessary:
//to keep it after finished
animation.removedOnCompletion = false;
animation.fillMode = kCAFillModeBoth;
I solved the problem with extra layers like this:
- (void)_drawStrokeOnCircle {
[self _initCircle];
[CATransaction begin];
for (NSUInteger i = 0; i < 4; i++)
{
CAShapeLayer* strokePart = [[CAShapeLayer alloc] init];
strokePart.fillColor = [[UIColor clearColor] CGColor];
strokePart.frame = _circle.bounds;
strokePart.path = _circle.path;
strokePart.lineCap = _circle.lineCap;
strokePart.lineWidth = _circle.lineWidth;
// Configure the apperence of the circle
strokePart.strokeColor = ((UIColor *)_colors[i]).CGColor;
strokePart.strokeStart = [_segmentsStart[i] floatValue];
strokePart.strokeEnd = [_segmentsEnd[i] floatValue];
[_circle addSublayer: strokePart];
// Configure animation
CAKeyframeAnimation* drawAnimation = [CAKeyframeAnimation animationWithKeyPath: #"strokeEnd"];
drawAnimation.duration = 4.0;
// Animate from no part of the stroke being drawn to the entire stroke being drawn
NSArray* times = #[ #(0.0),
#(strokePart.strokeStart),
#(strokePart.strokeEnd),
#(1.0) ];
NSArray* values = #[ #(strokePart.strokeStart),
#(strokePart.strokeStart),
#(strokePart.strokeEnd),
#(strokePart.strokeEnd) ];
drawAnimation.keyTimes = times;
drawAnimation.values = values;
drawAnimation.removedOnCompletion = NO;
drawAnimation.fillMode = kCAFillModeForwards;
// Experiment with timing to get the appearence to look the way you want
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[strokePart addAnimation: drawAnimation forKey: #"drawCircleAnimation"];
}
[CATransaction commit];
}
- (void)_initCircle {
[_circle removeFromSuperlayer];
_circle = [CAShapeLayer layer];
_radius = 100;
// Make a circular shape
_circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0 * _radius, 2.0 * _radius) cornerRadius:_radius].CGPath;
// Center the shape in self.view
_circle.position = CGPointMake(CGRectGetMidX(self.view.frame) - _radius,
CGRectGetMidY(self.view.frame) - _radius);
_circle.fillColor = [UIColor clearColor].CGColor;
_circle.lineWidth = 5;
// Add to parent layer
[self.view.layer addSublayer:_circle];
}
SWIFT 4, Xcode 11:
let strokeIt = CABasicAnimation(keyPath: "strokeEnd")
let timeLeftShapeLayer = CAShapeLayer()
and in your viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
drawTimeLeftShape()
strokeIt.fillMode = CAMediaTimingFillMode.forwards
strokeIt.isRemovedOnCompletion = false
strokeIt.fromValue = 0
strokeIt.toValue = 1
strokeIt.duration = 2
timeLeftShapeLayer.add(strokeIt, forKey: nil)
}
and by UIBezierPathcreate function to draw circle:
func drawTimeLeftShape() {
timeLeftShapeLayer.path = UIBezierPath(
arcCenter: CGPoint(x: myView.frame.midX , y: myView.frame.midY),
radius: myView.frame.width / 2,
startAngle: -90.degreesToRadians,
endAngle: 270.degreesToRadians,
clockwise: true
).cgPath
timeLeftShapeLayer.strokeColor = UIColor.red.cgColor
timeLeftShapeLayer.fillColor = UIColor.clear.cgColor
timeLeftShapeLayer.lineWidth = 1
timeLeftShapeLayer.strokeEnd = 0.0
view.layer.addSublayer(timeLeftShapeLayer)
}

CALayer animate contents between more than 2 images impossible?

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

How can I create an CABasicAnimation for multiple properties?

I have this code to animate a CALayer element.
CABasicAnimation *makeBiggerAnim=[CABasicAnimation animationWithKeyPath:#"radius"];
makeBiggerAnim.duration=0.2;
makeBiggerAnim.fromValue=[NSNumber numberWithDouble:20.0];
makeBiggerAnim.toValue=[NSNumber numberWithDouble:40.0];
makeBiggerAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
My question is, now everything works fine, I would like another attribute of the same element at the same time. I've seen you can do additive animations and stuff.
My question is:
Is the additive attribute the best / only way to do that? (animating at once multiple properties of the same object at once )
Thanks!
You can create an CAAnimationGroup and customize the duration and timing function on it. Then you create all your CABasicAnimations, set their to value and add them to the animation group. Finally, you add the animation group to the layer that you are animating.
Here an example:
CABasicAnimation *makeBiggerAnim=[CABasicAnimation animationWithKeyPath:#"cornerRadius"];
makeBiggerAnim.fromValue=[NSNumber numberWithDouble:20.0];
makeBiggerAnim.toValue=[NSNumber numberWithDouble:40.0];
CABasicAnimation *fadeAnim=[CABasicAnimation animationWithKeyPath:#"opacity"];
fadeAnim.fromValue=[NSNumber numberWithDouble:1.0];
fadeAnim.toValue=[NSNumber numberWithDouble:0.0];
CABasicAnimation *rotateAnim=[CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
rotateAnim.fromValue=[NSNumber numberWithDouble:0.0];
rotateAnim.toValue=[NSNumber numberWithDouble:M_PI_4];
// Customizing the group with duration etc, will apply to all the
// animations in the group
CAAnimationGroup *group = [CAAnimationGroup animation];
group.duration = 0.2;
group.repeatCount = 3;
group.autoreverses = YES;
group.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
group.animations = #[makeBiggerAnim, fadeAnim, rotateAnim];
[myLayer addAnimation:group forKey:#"allMyAnimations"];
let groupAnimation = CAAnimationGroup()
groupAnimation.beginTime = CACurrentMediaTime() + 0.5
groupAnimation.duration = 0.5
let scaleDown = CABasicAnimation(keyPath: "transform.scale")
scaleDown.fromValue = 3.5
scaleDown.toValue = 1.0
let rotate = CABasicAnimation(keyPath: "transform.rotation")
rotate.fromValue = .pi/10.0
rotate.toValue = 0.0
let fade = CABasicAnimation(keyPath: "opacity")
fade.fromValue = 0.0
fade.toValue = 1.0
groupAnimation.animations = [scaleDown,rotate,fade]
loginButton.layer.add(groupAnimation, forKey: nil)
This is for the newest update on swift (swift 3). Your code should include a object at the end, i.e. UIButton, UILabel, something that you can animate. In my code it was the loginButton (which was the title or name).
In Swift-3 we can use CAAnimationGroup as below :-
let position = CAKeyframeAnimation(keyPath: "position")
position.values = [ NSValue.init(cgPoint: .zero) , NSValue.init(cgPoint: CGPoint(x: 0, y: -20)) , NSValue.init(cgPoint: .zero) ]
position.timingFunctions = [ CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut), CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) ]
position.isAdditive = true
position.duration = 1.2
let rotation = CAKeyframeAnimation(keyPath: "transform.rotation")
rotation.values = [ 0, 0.14, 0 ]
rotation.duration = 1.2
rotation.timingFunctions = [ CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut), CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) ]
let fadeAndScale = CAAnimationGroup()
fadeAndScale.animations = [ position, rotation]
fadeAndScale.duration = 1

Animating CALayer of UIView to round corners

I'm trying to animate the rounding of the corners of my view. The cornerRadius property is listed as animatible, but I can't seem to get it to work. I actually can't get any of the other properties to animate either, but the corners are what I'm interested in. Here's my code, and it's pretty darn simple:
[CATransaction begin];
[CATransaction setValue: [NSNumber numberWithFloat: 2.0f] forKey:kCATransactionAnimationDuration];
self.myView.layer.cornerRadius = 50.0f;
[CATransaction commit];
What am I missing here guys? The corners become rounded, but it's instantaneous instead of taking 2 seconds.
CABasicAnimation *anim1 = [CABasicAnimation animationWithKeyPath:#"cornerRadius"];
anim1.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
anim1.fromValue = [NSNumber numberWithFloat:0.0f];
anim1.toValue = [NSNumber numberWithFloat:50.0f];
anim1.duration = 2.0;
[self.myView.layer addAnimation:anim1 forKey:#"cornerRadius"];
Swift 4.2/5:
let anim1 = CABasicAnimation(keyPath: "cornerRadius")
anim1.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
anim1.fromValue = 0
anim1.toValue = 50
anim1.duration = 2.0
layer.add(anim1, forKey: "cornerRadius")
Swift 3/4.0:
let anim1 = CABasicAnimation(keyPath: "cornerRadius")
anim1.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
anim1.fromValue = 0
anim1.toValue = 50
anim1.duration = 2.0
myView.layer.add(anim1, forKey: "cornerRadius")
Update:
As Mark noticed, it's cleaner to use #keyPath to describe the property to be animated:
let anim1 = CABasicAnimation(keyPath: #keyPath(CALayer.cornerRadius))
Seems like you're overthinking it. I was able to get by without using CoreAnimation. Here's what I did:
UIView.animate(withDuration: animationDuration) {
self.myView.layer.cornerRadius = 10.0
}

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