29

how can I add an animation to a UITextField to indicate wrong password exactly like the one in facebook app (at login screen) or the Mac OS X login box ?

thank you in advance.

JAHelia
  • 6,934
  • 17
  • 74
  • 134
  • 2
    http://stackoverflow.com/a/9371196/294884 – Fattie Jun 22 '14 at 20:03
  • Possible duplicate of [Shake visual effect on iPhone (NOT shaking the device)](http://stackoverflow.com/questions/1632364/shake-visual-effect-on-iphone-not-shaking-the-device) – stefreak Jul 25 '16 at 22:25

12 Answers12

43

Something like that

-(void)shake:(UIView *)theOneYouWannaShake
{
  [UIView animateWithDuration:0.03 animations:^
                                  {
                                    theOneYouWannaShake.transform = CGAffineTransformMakeTranslation(5*direction, 0);
                                  } 
                                  completion:^(BOOL finished) 
                                  {
                                    if(shakes >= 10)
                                    {
                                      theOneYouWannaShake.transform = CGAffineTransformIdentity;
                                      return;
                                    }
                                    shakes++;
                                    direction = direction * -1;
                                    [self shake:theOneYouWannaShake];
                                  }];
}

So you need three more things: An int direction which is set to 1 before the shake is called an int shakes, which is set to 0 before the shake is called and a constant MAX_SHAKES which is as large as you like. Hope that helps.

EDIT:

call it like this:

  direction = 1;
  shakes = 0;
  [self shake:aUIView];

inside header file add

int direction;
int shakes;
Kai Huppmann
  • 10,705
  • 6
  • 47
  • 78
  • 1
    it did not work, the text field moves only to right side and stops. I've applied all three values you mentioned. – JAHelia Apr 24 '12 at 11:38
  • @Kai: Which framework is required to support above animation? – Deviator Apr 24 '12 at 12:10
  • @JAHelia ok seems as if `shakes >= MAX_SHAKES` evaluates to `YES`. Test with logging or debugging if that's the case. If I'm right: Why is it true, i.e. what are the values of `shakes` and `MAX_SHAKES`? If not maybe the `direction` doesn't change, but stays `1`. Check that too by logging or debugging. @M.Sharjeel Just UIKit and CoreGraphics – Kai Huppmann Apr 24 '12 at 12:31
  • @JAHelia Just tested the code and it works (it had a little typo, but I think you found it, because otherwise it wouldn't have compiled). I edited the answer with some other values and MAX_SHAKES hard coded, so that there's no need for a constant any more. – Kai Huppmann Apr 24 '12 at 13:13
  • 1
    @Kai, thank you so much, after you updated your code it has worked (I of course fixed the typo the first time I run the app). anyway, is it possible to make the animation a little bit smoother, if you have a look at the login box of Mac OSX Lion and enter a wrong password you will notice that the animation bouncing is smoother than our case here. – JAHelia Apr 24 '12 at 13:34
  • 1
    @JAHelia As I said, I tested and then updated the values inside the code a bit. To me it looks fine now, but maybe you want to slow down a bit (change duration param) or largen/smallen the way in each direction by changing the 5 in CGAffineTransformMakeTranslation(5*direction, 0). If you want to smoothen everything you can also test with different animation curves then you have to change to: UIView animateWithDuration:0.05 delay:.0 options: UIViewAnimationCurveEaseInOut animations: ... completion: ...]; – Kai Huppmann Apr 24 '12 at 13:48
  • Amazing answer. Not only I used this example as is, I now have a pretty solid understanding of ios animations. By the way, instead of using global variables, you could provide them as arguments to the methods. – Hgeg Sep 07 '12 at 22:59
  • @fnc12 just use as described above. U need two properties direction and shakes. – Kai Huppmann Jan 29 '15 at 14:34
  • @fnc12 as I said: It's exactly like the example direction=1, will be changed in every loop. – Kai Huppmann Jan 30 '15 at 11:50
13

(Jan 16 2015) Update: (enum UIViewAnimationOptions) cast is fine and UIViewAnimationOptionCurveEaseOut is 2 << 16 per UIView.h under typedef NS_OPTIONS(NSUInteger, UIViewAnimationOptions)

(Jan 31 2013) Further modified Kai's answer to include:

  1. edge delay of 0.01s
  2. easeInOut
  3. reduce duration of shakes every shake from 0.09 to 0.04
  4. throttle down movement by a pt every 1 complete loop (right-left-right)

Note: if you plan on shaking two controls (email and password) together you might want to avoid using class or static variables for shakes and translate. Instead, initialize and pass shake and translate as parameters. I used statics so no class variables needed.

-(void)shakeAnimation:(UIView*) view {
    const int reset = 5;
    const int maxShakes = 6;

    //pass these as variables instead of statics or class variables if shaking two controls simultaneously
    static int shakes = 0;
    static int translate = reset;

    [UIView animateWithDuration:0.09-(shakes*.01) // reduce duration every shake from .09 to .04 
                          delay:0.01f//edge wait delay
                        options:(enum UIViewAnimationOptions) UIViewAnimationCurveEaseInOut
                     animations:^{view.transform = CGAffineTransformMakeTranslation(translate, 0);}
                     completion:^(BOOL finished){
                         if(shakes < maxShakes){
                             shakes++;

                             //throttle down movement
                             if (translate>0)
                                 translate--;

                             //change direction
                             translate*=-1;
                             [self shakeAnimation:view];
                         } else {
                             view.transform = CGAffineTransformIdentity;
                             shakes = 0;//ready for next time
                             translate = reset;//ready for next time
                             return;
                         }
                     }];
}
Dickey Singh
  • 713
  • 1
  • 8
  • 16
  • 2
    The `(enum UIViewAnimationOptions) UIViewAnimationCurveEaseInOut` is a mistake and will be translated to `UIViewAnimationOptionLayoutSubviews`. You should pass `UIViewAnimationOptionCurveEaseInOut` which is of the expected `UIViewAnimationOptions` type. – Guillaume Algis Aug 01 '13 at 12:12
  • @GuillaumeAlgis Thanks. I am still getting a "Unable to simultaneously satisfy constraints." error when I run it on iPad. It does work and run, just with console errors. Can you show what you mean? Did you mean to substitute this in? "options:(UIViewAnimationOptions) UIViewAnimationOptionCurveEaseInOut" Thx! – B-Money Sep 02 '13 at 16:33
  • I meant to replace `(enum UIViewAnimationOptions) UIViewAnimationCurveEaseInOut` by `UIViewAnimationOptionCurveEaseInOut`. The "Unable to simultaneously satisfy constraints" error is not related to this but to AutoLayout. – Guillaume Algis Sep 02 '13 at 20:12
9

This Swift 2.0 answer requires no recursion and no loops. Just leverages CABasicAnimation by refining this SO answer:

func shakeView(shakeView: UIView) {
    let shake = CABasicAnimation(keyPath: "position")
    let xDelta = CGFloat(5)
    shake.duration = 0.15
    shake.repeatCount = 2
    shake.autoreverses = true

    let from_point = CGPointMake(shakeView.center.x - xDelta, shakeView.center.y)
    let from_value = NSValue(CGPoint: from_point)

    let to_point = CGPointMake(shakeView.center.x + xDelta, shakeView.center.y)
    let to_value = NSValue(CGPoint: to_point)

    shake.fromValue = from_value
    shake.toValue = to_value
    shake.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
    shakeView.layer.addAnimation(shake, forKey: "position")
}

Updated for Swift 4:

func shakeView(_ shakeView: UIView) {
    let shake = CABasicAnimation(keyPath: "position")
    let xDelta = CGFloat(5)
    shake.duration = 0.15
    shake.repeatCount = 2
    shake.autoreverses = true

    let from_point = CGPoint(x: shakeView.center.x - xDelta, y: shakeView.center.y)
    let from_value = NSValue(cgPoint: from_point)

    let to_point = CGPoint(x: shakeView.center.x + xDelta, y: shakeView.center.y)
    let to_value = NSValue(cgPoint: to_point)

    shake.fromValue = from_value
    shake.toValue = to_value
    shake.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
    shakeView.layer.add(shake, forKey: "position")
}
Linus Unnebäck
  • 23,234
  • 15
  • 74
  • 89
Crashalot
  • 33,605
  • 61
  • 269
  • 439
8

Based on a previous answer as swift method ready to use :

func shakeTextField(textField: UITextField, numberOfShakes: Int, direction: CGFloat, maxShakes: Int) {

    let interval: TimeInterval = 0.03

    UIView.animate(withDuration: interval, animations: { () -> Void in
        textField.transform = CGAffineTransform(translationX: 5 * direction, y: 0)

        }, completion: { (aBool :Bool) -> Void in

            if (numberOfShakes >= maxShakes) {
                textField.transform = .identity
                textField.becomeFirstResponder()
                return
            }

            self.shakeTextField(textField: textField, numberOfShakes: numberOfShakes + 1, direction: direction * -1, maxShakes: maxShakes)
    })

}

To call it :

shakeTextField(aTextField,numberOfShakes:0, direction :1, maxShakes : 10)
ant_one
  • 845
  • 11
  • 11
3

If you came here looking for a MonoTouch answer, here is a rough translation of Dickey's code:

public static void /*Harlem*/Shake (this UIView view, int shakes = 6, int translation = 5)
{
    UIView.Animate (0.03 + (shakes * 0.01), 0.01, UIViewAnimationOptions.CurveEaseInOut, () => {
        view.Transform = CGAffineTransform.MakeTranslation (translation, 0);
    }, () => {
       if (shakes == 0) {
            view.Transform = CGAffineTransform.MakeIdentity ();
            return;
        }

        if (translation > 0)
            translation --;

        translation *= -1;
        shakes --;

        Shake (view, shakes, translation);
    });
}

Put it with the rest of your extensions methods and call like that:

password.Shake ();
Community
  • 1
  • 1
Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
3

iOS iPhone textbox password window view shaking

I created a category method for UIView that can be used to shake any element - e.g. a UITextField - with the ability to get notified after the shaking has ended. Here is how to use it:

[myPasswordField shake];

// Or with a callback after the shake 
[myPasswordField shakeWithCallback:^{
   NSLog(@"Shaking has ended");
}];

Here is the code.

UIView+Shake.h

#import <UIKit/UIKit.h>

@interface UIView (UIView_Shake)

-(void)shake;
-(void)shakeWithCallback:(void (^)(void))completeBlock;

@end

UIView+Shake.m

#import "UIView+Shake.h"
#import <objc/runtime.h>

@implementation UIView (UIView_Shake)

static void *NumCurrentShakesKey;
static void *NumTotalShakesKey;
static void *ShakeDirectionKey;

- (int)numCurrentShakes {
    return [objc_getAssociatedObject(self, &NumCurrentShakesKey) intValue];
}

- (void)setNumCurrentShakes:(int)value {
    objc_setAssociatedObject(self, &NumCurrentShakesKey, [NSNumber numberWithInt:value], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (int)numTotalShakes {
    return [objc_getAssociatedObject(self, &NumTotalShakesKey) intValue];
}

- (void)setNumTotalShakes:(int)value {
    objc_setAssociatedObject(self, &NumTotalShakesKey, [NSNumber numberWithInt:value], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (int)shakeDirection {
    return [objc_getAssociatedObject(self, &ShakeDirectionKey)  intValue];
}

- (void)setShakeDirection:(int)value {
    objc_setAssociatedObject(self, &ShakeDirectionKey, [NSNumber numberWithInt:value], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(void)shake {
    [self shakeNextWithCompleteBlock:nil];
}

-(void)shakeWithCallback:(void (^)(void))completeBlock {
    self.numCurrentShakes = 0;
    self.numTotalShakes = 6;
    self.shakeDirection = 8;
    [self shakeNextWithCompleteBlock:completeBlock];
}

-(void)shakeNextWithCompleteBlock:(void (^)(void))completeBlock
{
    UIView* viewToShake = self;
    [UIView animateWithDuration:0.08
                     animations:^
     {
         viewToShake.transform = CGAffineTransformMakeTranslation(self.shakeDirection, 0);
     }
                     completion:^(BOOL finished)
     {
         if(self.numCurrentShakes >= self.numTotalShakes)
         {
             viewToShake.transform = CGAffineTransformIdentity;
             if(completeBlock != nil) {
                 completeBlock();
             }
             return;
         }
         self.numCurrentShakes++;
         self.shakeDirection = self.shakeDirection * -1;
         [self shakeNextWithCompleteBlock:completeBlock];
     }];
}

@end
Gergely Orosz
  • 6,475
  • 4
  • 47
  • 59
2

Here's my spin on it:

@implementation UITextField (Shake)

- (void)shake {
    [self shakeWithIterations:0 direction:1 size:4];
}

#pragma mark - Private

- (void)shakeWithIterations:(int)iterations direction:(int)direction size:(int)size {
    [UIView animateWithDuration:0.09-(iterations*.01) animations:^{
        self.transform = CGAffineTransformMakeTranslation(size*direction, 0);
    } completion:^(BOOL finished) {
        if (iterations >= 5 || size <= 0) {
            self.transform = CGAffineTransformIdentity;
            return;
        }
        [self shakeWithIterations:iterations+1 direction:direction*-1 size:MAX(0, size-1)];
    }];
}

@end
Chris
  • 39,719
  • 45
  • 189
  • 235
2

I tried @stefreak solution but the loop approach doesn't work on iOS 7.1. So I combined the solutions from @stefreak and @Chris, and added the completion block to be notified when the shaking finishes. Here is my code:

- (void)shakeView:(UIView *)view iterations:(NSInteger)iterations direction:(NSInteger)direction completion:(void (^)())completion
{
    const NSInteger MAX_SHAKES = 6;
    const CGFloat SHAKE_DURATION = 0.05;
    const CGFloat SHAKE_TRANSFORM = 10.0;

    [UIView animateWithDuration:SHAKE_DURATION
                          delay:0.0
                        options:UIViewAnimationOptionCurveEaseIn
                     animations:^{
                         view.transform = iterations >= MAX_SHAKES ? CGAffineTransformIdentity : CGAffineTransformMakeTranslation(SHAKE_TRANSFORM * direction, 0);
                     } completion:^(BOOL finished) {
                         if (finished)
                         {
                             if (iterations >= MAX_SHAKES)
                             {
                                 if (completion)
                                 {
                                     completion();
                                 }
                             }
                             else
                             {
                                 [self shakeView:view iterations:(iterations + 1) direction:(direction * -1) completion:completion];
                             }
                         }
                     }];
}

- (void)shakeView:(UIView *)view completion:(void (^)())completion
{
    [self shakeView:view iterations:0 direction:1 completion:completion];
}
boherna
  • 659
  • 8
  • 7
1

You can also do it using basic animation

let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.09
animation.repeatCount = 4
animation.autoreverses = true
animation.fromValue = NSValue(CGPoint: CGPointMake(txtField.center.x - 10, txtField.center.y))
animation.toValue = NSValue(CGPoint: CGPointMake(txtField.center.x + 10, txtField.center.y))
txtField.layer.addAnimation(animation, forKey: "position")

Here you can the change duration,repeatCount.Changing into the fromValue and toValue will change distance moved in the shake

Donal
  • 6,082
  • 2
  • 19
  • 30
1

Since the question was about Objective-C, and since I am using Objective-C in my project, I think this Objective-C translation of this previous Swift answer could be useful to someone else:

- (void)shakeView:(UIView*)view
{
    CABasicAnimation *shake = [CABasicAnimation animationWithKeyPath:@"position"];
    CGFloat xDelta = 5.0;
    shake.duration = 0.15;
    shake.repeatCount = 2;
    shake.autoreverses = YES;

    CGPoint fromPoint = CGPointMake(view.center.x - xDelta, view.center.y);
    CGPoint toPoint = CGPointMake(view.center.x + xDelta, view.center.y);

    shake.fromValue = [NSValue valueWithCGPoint:fromPoint];
    shake.toValue = [NSValue valueWithCGPoint:toPoint];
    shake.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    [view.layer addAnimation:shake forKey:@"position"];
}
gog
  • 1,220
  • 11
  • 30
0

There's a Swift Library for animating Textfield in github here. Simply import the swift file and implement as below

// Shake with the default speed
self.textField.shake(10, delta:5) //10 no. of shakes with 5 points wide

// Shake with a custom speed
self.sampleText.shake(10, delta: 5, speed: 0.10) //10 no. of shakes with 5 points wide in 100ms per shake
Nimit Parekh
  • 16,776
  • 8
  • 50
  • 72
Rugmangathan
  • 3,186
  • 6
  • 33
  • 44
0

Swift 3 and stack_view instaed textField

func shakeTextField (stack_view : UIStackView, numberOfShakes : Int, direction: CGFloat, maxShakes : Int) {
        let interval : TimeInterval = 0.05

        UIView.animate(withDuration: interval, animations: { () -> Void in
            stack_view.transform = CGAffineTransform(translationX: 5 * direction, y: 0)

        }, completion: { (aBool :Bool) -> Void in

            if (numberOfShakes >= maxShakes) {
                stack_view.becomeFirstResponder()
                return
            }
            self.shakeTextField(stack_view: stack_view, numberOfShakes: numberOfShakes + 1, direction: direction * -1, maxShakes: maxShakes )
        })
    }
Ppillo
  • 31
  • 3