animating UITextField to indicate a wrong password

2019-01-30 12:07发布

问题:

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.

回答1:

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;


回答2:

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


回答3:

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


回答4:

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

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

    let interval : NSTimeInterval = 0.03

    UIView.animateWithDuration(interval, animations: { () -> Void in
        textField.transform = CGAffineTransformMakeTranslation(5 * direction, 0)

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

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

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

    })

}

To call it :

shakeTextField(aTextField,numberOfShakes:0, direction :1, maxShakes : 10)


回答5:

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 ();


回答6:

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


回答7:

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


回答8:

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


回答9:

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



回答10:

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


回答11:

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


回答12:

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