CABasicAnimation lags (iOS5) - ios

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).

Related

How to change the starting point or anchor point of animations using pop or CoreAnimation

The following code starts a scale animation and it expands along XY from the center of the UIButton. How to make it start from the left or frame.origin.x = 0
POPSpringAnimation *anim = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerBounds];
anim.fromValue = [NSValue valueWithCGRect:CGRectMake(signupButton.frame.origin.x,
signupButton.frame.origin.y,
0,
signupButton.frame.size.height)];
anim.toValue = [NSValue valueWithCGRect:signupButton.frame];
[anim setValue:#"progressBar" forKey:#"animName"];
anim.delegate = self;
// I tried to set anchor point but without any luck
// signupButton.layer.anchorPoint = CGPointMake(150, signupButton.frame.origin.y);
[signupButton.layer pop_addAnimation:anim forKey:#"signupButton"];
You are animating the bounds, but passing in the frame. You are correct in wanting to animate bounds.
So, I think this would be the correct code.
POPSpringAnimation *anim = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerBounds];
CGRect startRect = signupButton.layer.bounds;
startRect.size.width = 0.0;
anim.fromValue = [NSValue valueWithCGRect:startRect];
anim.toValue = [NSValue valueWithCGRect:signupButton.layer.bounds];
[anim setValue:#"progressBar" forKey:#"animName"];
anim.delegate = self;
//anchor on the center left
CGPoint center = signupButton.layer.position;
signupButton.layer.anchorPoint = CGPointMake(0, 0.5);
signupButton.layer.position = CGPointMake(center.x - signupButton.layer.bounds.size.width * 0.5, center.y);
[signupButton.layer pop_addAnimation:anim forKey:#"signupButton"];

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)
}

CABasicAnimation how to get current angle of rotation

I have some problem with CABasicAnimation. It`s similar to that post: CABasicAnimation rotate returns to original position
So, i have uiimageview that rotate in touchMove. In touchEnd invoke method that do "animation of inertia" :
-(void)animationRotation: (float)beginValue
{
CABasicAnimation *anim;
anim = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
anim.duration = 0.5;
anim.repeatCount = 1;
anim.fillMode = kCAFillModeForwards;
anim.fromValue = [NSNumber numberWithFloat:beginValue];
[anim setDelegate:self];
anim.toValue = [NSNumber numberWithFloat:(360*M_PI/180 + beginValue)];
[appleView.layer addAnimation:anim forKey:#"transform"];
CGAffineTransform rot = CGAffineTransformMakeRotation(360*M_PI/180 + beginValue);
appleView.transform = rot;
}
This animation works fine, but if I invoke touchBegan before animationRotation ended, angle of rotation is beginValue. I need cath current angle of rotation. As an experiment, i declare method
-(vod) animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
NSLog(#"Animation finished!");
}
and it's seems working. but I don't know how get that value of angle or CGAffineTransform for my UIImageView in animationDidStop.
It's even possible to do? Thanks.
you should use presentationLayer method to get layer properties during animation in flight.
so your code should be like this,
#define RADIANS_TO_DEGREES(__ANGLE__) ((__ANGLE__) / (float)M_PI * 180.0f)
-(void)animationRotation: (float)beginValue
{
CABasicAnimation *anim;
anim = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
anim.duration = 0.5;
anim.repeatCount = 1;
anim.fillMode = kCAFillModeForwards;
anim.fromValue = [NSNumber numberWithFloat:beginValue];
[anim setDelegate:self];
//get current layer angle during animation in flight
CALayer *currentLayer = (CALayer *)[appleView.layer presentationLayer];
float currentAngle = [(NSNumber *)[currentLayer valueForKeyPath:#"transform.rotation.z"] floatValue];
currentAngle = roundf(RADIANS_TO_DEGREES(currentAngle));
NSLog(#"current angle: %f",currentAngle);
anim.toValue = [NSNumber numberWithFloat:(360*M_PI/180 + beginValue)];
[appleView.layer addAnimation:anim forKey:#"transform"];
CGAffineTransform rot = CGAffineTransformMakeRotation(360*M_PI/180 + beginValue);
appleView.transform = rot;
}

UIView shake animation

i'm trying to make a UIView shake when a button is pressed.
I am adapting the code I found on http://www.cimgf.com/2008/02/27/core-animation-tutorial-window-shake-effect/.
However, by trying to adapt the following code to shake a UIView, it does not work:
- (void)animate {
const int numberOfShakes = 8;
const float durationOfShake = 0.5f;
const float vigourOfShake = 0.1f;
CAKeyframeAnimation *shakeAnimation = [CAKeyframeAnimation animation];
CGRect frame = lockView.frame;
CGMutablePathRef shakePath = CGPathCreateMutable();
CGPathMoveToPoint(shakePath, NULL, CGRectGetMinX(frame), CGRectGetMinY(frame));
for (int index = 0; index < numberOfShakes; ++index) {
CGPathAddLineToPoint(shakePath, NULL, CGRectGetMinX(frame) - frame.size.width * vigourOfShake, CGRectGetMinY(frame));
CGPathAddLineToPoint(shakePath, NULL, CGRectGetMinX(frame) + frame.size.width * vigourOfShake, CGRectGetMinY(frame));
}
CGPathCloseSubpath(shakePath);
shakeAnimation.path = shakePath;
shakeAnimation.duration = durationOfShake;
[lockView.layer addAnimation:shakeAnimation forKey:#"frameOrigin"];
}
I wrote that post. It's overkill for a UIView, plus the parameters are geared toward an OSX app. Do this instead.
CABasicAnimation *animation =
[CABasicAnimation animationWithKeyPath:#"position"];
[animation setDuration:0.05];
[animation setRepeatCount:8];
[animation setAutoreverses:YES];
[animation setFromValue:[NSValue valueWithCGPoint:
CGPointMake([lockView center].x - 20.0f, [lockView center].y)]];
[animation setToValue:[NSValue valueWithCGPoint:
CGPointMake([lockView center].x + 20.0f, [lockView center].y)]];
[[lockView layer] addAnimation:animation forKey:#"position"];
You'll have to play with the duration and repeatCount parameters as well as the x distance from center in the from and to values, but it should give you what you need.
Swift 3.0
let midX = lockView.center.x
let midY = lockView.center.y
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.06
animation.repeatCount = 4
animation.autoreverses = true
animation.fromValue = CGPoint(x: midX - 10, y: midY)
animation.toValue = CGPoint(x: midX + 10, y: midY)
layer.add(animation, forKey: "position")
I prefer this solution that has a nice springy behavior, ideal for a wrong-password shake animation.
view.transform = CGAffineTransformMakeTranslation(20, 0);
[UIView animateWithDuration:0.4 delay:0.0 usingSpringWithDamping:0.2 initialSpringVelocity:1.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
view.transform = CGAffineTransformIdentity;
} completion:nil];
Swift 3
extension UIView {
func shake() {
self.transform = CGAffineTransform(translationX: 20, y: 0)
UIView.animate(withDuration: 0.4, delay: 0, usingSpringWithDamping: 0.2, initialSpringVelocity: 1, options: .curveEaseInOut, animations: {
self.transform = CGAffineTransform.identity
}, completion: nil)
}
}
Here's my nice and simple looking version This simulates the shake you get on Mac OS X when you do an incorrect login. You could add this as a category on UIView if you like.
#implementation UIView (DUExtensions)
- (void) shake {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.translation.x"];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
animation.duration = 0.6;
animation.values = #[ #(-20), #(20), #(-20), #(20), #(-10), #(10), #(-5), #(5), #(0) ];
[self.layer addAnimation:animation forKey:#"shake"];
}
#end
The animation values are the x offset from the views current position. Positive values shifting the view to the right, and negative values to the left. By successively lowering them, you get a shake that naturally loses momentum. You can tweak these numbers if you like.
Here is the swift version as an extension in case anybody needs it
extension UIImageView{
func vibrate(){
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.05
animation.repeatCount = 5
animation.autoreverses = true
animation.fromValue = NSValue(CGPoint: CGPointMake(self.center.x - 2.0, self.center.y))
animation.toValue = NSValue(CGPoint: CGPointMake(self.center.x + 2.0, self.center.y))
self.layer.addAnimation(animation, forKey: "position")
}
}
This will animate an small UIImageView (around 15x15). If you need to animate something bigger you may want to change the 2.0 factor of movement to something greater.
Based on #bandejapaisa answer, UIView extension for Swift 3
extension UIView {
func shake() {
let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.duration = 0.6
animation.values = [-20, 20, -20, 20, -10, 10, -5, 5, 0]
layer.addAnimation(animation, forKey: "shake")
}
}
You can try this piece of code:
to call the code below, use: [self earthquake:myObject];
#pragma mark EarthQuake Methods
- (void)earthquake:(UIView*)itemView
{
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
CGFloat t = 2.0;
CGAffineTransform leftQuake = CGAffineTransformTranslate(CGAffineTransformIdentity, t, -t);
CGAffineTransform rightQuake = CGAffineTransformTranslate(CGAffineTransformIdentity, -t, t);
itemView.transform = leftQuake; // starting point
[UIView beginAnimations:#"earthquake" context:itemView];
[UIView setAnimationRepeatAutoreverses:YES]; // important
[UIView setAnimationRepeatCount:3];
[UIView setAnimationDuration:0.05];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(earthquakeEnded:finished:context:)];
itemView.transform = rightQuake; // end here & auto-reverse
[UIView commitAnimations];
}
- (void)earthquakeEnded:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{
if ([finished boolValue])
{
UIView* item = (UIView *)context;
item.transform = CGAffineTransformIdentity;
}
}
You can call this method on UIButton click event
-(void)shakescreen
{
//Shake screen
CGFloat t = 5.0;
CGAffineTransform translateRight = CGAffineTransformTranslate(CGAffineTransformIdentity, t, t);
CGAffineTransform translateLeft = CGAffineTransformTranslate(CGAffineTransformIdentity, -t, -t);
self.view.transform = translateLeft;
[UIView animateWithDuration:0.05 delay:0.0 options:UIViewAnimationOptionAutoreverse|UIViewAnimationOptionRepeat animations:^
{
[UIView setAnimationRepeatCount:2.0];
self.view.transform = translateRight;
} completion:^(BOOL finished)
{
if (finished)
{
[UIView animateWithDuration:0.05 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^
{
self.view.transform = CGAffineTransformIdentity;
}
completion:NULL];
}
}];
}
Hope this will help you :-)
C# Xamarin.iOS version of answer how to create UIView shake animation in iOS is below
CAKeyFrameAnimation keyframeAnimation = CAKeyFrameAnimation.GetFromKeyPath(new NSString("transform.translation.x"));
keyframeAnimation.TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.EaseInEaseOut);
keyframeAnimation.Duration = 0.6f;
keyframeAnimation.Values = new NSObject[]{ new NSNumber(-20f), new NSNumber(20f), new NSNumber(-20f), new NSNumber(20f), new NSNumber(-10f), new NSNumber(10f), new NSNumber(-5f), new NSNumber(5f), new NSNumber(0f) };
shakyView.Layer.AddAnimation(keyframeAnimation, "shake");
#imike answer in Swift 4.2
extension UIView {
func shake() {
let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
animation.duration = 0.6
animation.values = [-20, 20, -20, 20, -10, 10, -5, 5, 0]
self.layer.add(animation, forKey: "shake")
}}
Here's one that uses a damper function to decay the shake:
- (void)shake
{
CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
animation.duration = 0.5;
animation.delegate = self;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = YES;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
NSMutableArray* values = [[NSMutableArray alloc] init];
int steps = 100;
double position = 0;
float e = 2.71;
for (int t = 0; t < steps; t++)
{
position = 10 * pow(e, -0.022 * t) * sin(0.12 * t);
NSValue* value = [NSValue valueWithCGPoint:CGPointMake([self center].x - position, [self center].y)];
DDLogInfo(#"Value: %#", value);
[values addObject:value];
}
animation.values = values;
[[self layer] addAnimation:animation forKey:#"position"];
}
I refactored #Matt Long code and made a category to UIView. Now it's much more reusable and easy to use.
#implementation UIView (Animation)
- (void)shakeViewWithOffest:(CGFloat)offset {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position.x"];
[animation setDuration:0.05];
[animation setRepeatCount:6];
[animation setAutoreverses:YES];
[animation setFromValue:#([self center].x-offset)];
[animation setToValue:#([self center].x+offset)];
[self.layer addAnimation:animation forKey:#"position.x"];
}
- (void)shake {
[self shakeViewWithOffest:7.0f];
}
#end
Swift 3 implementation based on #Mihael-Isaev answer
private enum Axis: StringLiteralType {
case x = "x"
case y = "y"
}
extension UIView {
private func shake(on axis: Axis) {
let animation = CAKeyframeAnimation(keyPath: "transform.translation.\(axis.rawValue)")
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.duration = 0.6
animation.values = [-20, 20, -20, 20, -10, 10, -5, 5, 0]
layer.add(animation, forKey: "shake")
}
func shakeOnXAxis() {
self.shake(on: .x)
}
func shakeOnYAxis() {
self.shake(on: .y)
}
}
Swift 4.0:
Based on the top answer but a refinement over the animation: This does not have the jumps at the start and end of animation.
let midX = center.x
let midY = center.y
let rightAnim = CABasicAnimation(keyPath: #keyPath(CALayer.position))
rightAnim.duration = 0.07
rightAnim.autoreverses = true
rightAnim.fromValue = CGPoint(x: midX, y: midY)
rightAnim.toValue = CGPoint(x: midX + 9, y: midY)
let leftAnim = CABasicAnimation(keyPath: #keyPath(CALayer.position))
leftAnim.duration = 0.07
leftAnim.autoreverses = true
leftAnim.fromValue = CGPoint(x: midX, y: midY)
leftAnim.toValue = CGPoint(x: midX - 9, y: midY)
let group = CAAnimationGroup()
group.duration = leftAnim.duration + rightAnim.duration
group.animations = [rightAnim, leftAnim]
group.repeatCount = 3
layer.add(group, forKey: #keyPath(CALayer.position))
You can try the following code:
+ (void)vibrateView:(UIView*)view
{
CABasicAnimation *shiverAnimationR;
shiverAnimationR = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
shiverAnimationR.toValue = [NSNumber numberWithFloat:DEGREES_TO_RADIANS(1)];
//shiverAnimationR.toValue = [NSNumber numberWithFloat:DEGREES_TO_RADIANS(-10)];
shiverAnimationR.duration = 0.1;
shiverAnimationR.repeatCount = 1000000.0; // Use A high Value
shiverAnimationR.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[view.layer addAnimation: shiverAnimationR forKey:#"shiverAnimationR"];
CABasicAnimation * shiverAnimationL;
shiverAnimationL = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
//shiverAnimationL 2.toValue = [NSNumber numberWithFloat:DEGREES_TO_RADIANS(10)];
shiverAnimationL.toValue = [NSNumber numberWithFloat:DEGREES_TO_RADIANS(-1)];
shiverAnimationL.duration = 0.1;
shiverAnimationL.repeatCount = 1000000.0;
shiverAnimationL.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[view.layer addAnimation: shiverAnimationL forKey:#"shiverAnimationL"];
}
From the link.
Here is a version using,
+ (void)animateKeyframesWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewKeyframeAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
Introduced in iOS 7.
const CGFloat xDelta = 16.0f;
[UIView animateKeyframesWithDuration:0.50f
delay:0.0f
options:UIViewKeyframeAnimationOptionCalculationModeLinear
animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0
relativeDuration:(1.0/6.0)
animations:^{
self.passwordTextField.transform = self.usernameTextField.transform = CGAffineTransformMakeTranslation(xDelta, 0.0);
}];
[UIView addKeyframeWithRelativeStartTime:(1.0/6.0)
relativeDuration:(1.0/6.0)
animations:^{
self.passwordTextField.transform = self.usernameTextField.transform = CGAffineTransformMakeTranslation(-xDelta, 0.0);
}];
[UIView addKeyframeWithRelativeStartTime:(1.0/3.0)
relativeDuration:(1.0/3.0)
animations:^{
self.passwordTextField.transform = self.usernameTextField.transform = CGAffineTransformMakeTranslation(xDelta/2.0, 0.0);
}];
[UIView addKeyframeWithRelativeStartTime:(2.0/3.0)
relativeDuration:(1.0/3.0)
animations:^{
self.passwordTextField.transform = self.usernameTextField.transform = CGAffineTransformIdentity;
}];
}
completion:NULL];
Here is a UIView extension providing an awesome shake animation: https://gist.github.com/mourad-brahim/cf0bfe9bec5f33a6ea66
A Swift5 update is provided on the comments.

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