UIView-Shake-Animation

82

Ich versuche, ein UIView zum Schütteln zu bringen, wenn eine Taste gedrückt wird.

Ich passe den Code an, den ich auf http://www.cimgf.com/2008/02/27/core-animation-tutorial-window-shake-effect/ gefunden habe .

Wenn Sie jedoch versuchen, den folgenden Code anzupassen, um eine UIView zu erschüttern, funktioniert dies nicht:

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

}
Jack Greenhill
quelle

Antworten:

203

Ich habe diesen Beitrag geschrieben. Für ein UIView ist es übertrieben, und die Parameter sind auf eine OSX-App ausgerichtet. Tun Sie dies stattdessen.

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

Sie müssen mit den Parametern dauer und repeatCount sowie dem x-Abstand vom Zentrum in den Werten von und bis spielen, aber es sollte Ihnen das geben, was Sie brauchen. Ich hoffe das hilft. Lassen Sie mich wissen, wenn Sie Fragen haben.

--- ---.


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")
Matt Long
quelle
Vielen Dank, ich benutze Ihre Antwort, um es in Xamarin IOS zu erstellen. Wenn jemand es verwenden möchte, hier ist es: gist.github.com/jorwan/1ed57459c7b01b5a5b1135219e6219cf
Jorge Wander Santana Ureña
@Matt Was ist, wenn ich zufällig schütteln möchte, damit sich die UIView bei jedem Schütteln in zufällige Richtungen bewegt?
Ehtesham Hasan
@EhteshamHasan es konnte nicht wirklich zufällig sein. Die Positionen / Punkte müssten innerhalb eines Bereichs liegen, und Sie möchten möglicherweise nicht, dass sie zwischen den Extremen (z. B. Ecke zu Ecke) dieses Bereichs wackeln (obwohl dies bei Ihnen liegt). Die einfache Antwort besteht darin, die möglichen Positionen (CGPoints) in ein Array einzufügen und einen Zufallszahlengenerator zu erstellen, der einen Index liefert, der kleiner als die Anzahl der Punkte in Ihrem Array ist, und dann die Position mit derselben in meiner Antwort beschriebenen Technik an diesen Punkt zu verschieben.
Matt Long
78

Ich bevorzuge diese Lösung mit einem schönen federnden Verhalten, ideal für eine Shake-Animation mit falschem Passwort.

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)
    }
}
Ortwin Gentz
quelle
Wie wiederholst du das? es passiert gerade nur einmal.
Crashalot
1
Wenn ich das OP richtig verstehe, wollte er eine kurze Shake-Animation. Da im wirklichen Leben eine Schüttelbewegung Reibung hat und sich mit der Zeit verlangsamt, finde ich meine Lösung am besten geeignet. Wenn Sie das Schütteln verlängern möchten, experimentieren Sie mit den Parametern Dämpfung und Anfangsgeschwindigkeit. Wenn Sie es auf unbestimmte Zeit wiederholen möchten, wählen Sie eine der anderen Lösungen.
Ortwin Gentz
Diese Animation war die beste, stimmte zu. Ich möchte es nur ein paar Mal wiederholen, das ist alles.
Crashalot
Wenn Sie es nur erweitern möchten, versuchen Sie, mit den Werten für Dauer, Dämpfung und Anfangsgeschwindigkeit zu spielen.
Ortwin Gentz
1
Diese Lösung ist aufgrund der Möglichkeit, den Completion-Handler zu verwenden, viel besser.
Yuri Grigoriev
33

Hier ist meine schöne und einfach aussehende Version. Dies simuliert den Shake, den Sie unter Mac OS X erhalten, wenn Sie sich falsch anmelden. Sie können dies als Kategorie in UIView hinzufügen, wenn Sie möchten.

@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

Die Animationswerte sind der x-Versatz von der aktuellen Position der Ansicht. Positive Werte verschieben die Ansicht nach rechts und negative Werte nach links. Wenn Sie sie sukzessive absenken, erhalten Sie einen Shake, der natürlich an Dynamik verliert. Sie können diese Zahlen ändern, wenn Sie möchten.

Bandejapaisa
quelle
1
Tolles Aussehen und gute Idee, dies in eine UIView-Kategorie einzubetten. UIViews sollten zittern, Punkt!
Pedro Borges
Bei langsamerer Geschwindigkeit (dh längerer Dauer) ist diese Animation viel flüssiger als die der anderen Antwort, die repeatCount verwendet.
Theo
Einige Gedanken nach der Verwendung dieses Codes. Timing-Funktion - standardmäßig linear, daher nicht erforderlich, um sie einzustellen. Animationswerte - Sie könnten noch einen Schritt weiter gehen und eine Funktion definieren, um schöne mathematische Werte zu erzeugen ... das funktioniert aber auch :)
Jordan Smith
17

Hier ist die schnelle Version als Erweiterung für den Fall, dass jemand sie benötigt

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

Dadurch wird eine kleine UIImageView (ca. 15x15) animiert. Wenn Sie etwas Größeres animieren müssen, können Sie den Bewegungsfaktor 2.0 in etwas Größeres ändern.

Julio Bailon
quelle
8

Basierend auf der Antwort von @bandejapaisa, UIView-Erweiterung für 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")
    }
}
imike
quelle
4

Sie können diesen Code ausprobieren:

Verwenden Sie zum Aufrufen des folgenden Codes: [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;
   }
}
WrightsCS
quelle
4

Sie können diese Methode beim UIButton-Klickereignis aufrufen

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

Hoffe das hilft dir :-)

Birju
quelle
3

Die C # Xamarin.iOS-Version der Antwort zum Erstellen einer UIView-Shake-Animation unter iOS finden Sie weiter unten

        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");
Alex Sorokoletov
quelle
2

Hier ist eine, die eine Dämpferfunktion verwendet, um das Schütteln zu verringern:

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

}
Jasper Blues
quelle
2

Ich habe @ Matt Long Code überarbeitet und eine Kategorie erstellt UIView. Jetzt ist es viel wiederverwendbarer und einfacher zu bedienen.

@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
Szu
quelle
2

Swift 3-Implementierung basierend auf der Antwort von @ Mihael-Isaev

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)
    }
}
Lory Huz
quelle
2

@imike Antwort 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")
}}
iOS Lifee
quelle
1
Letzte Zeile sollte mit self.layer.add (Animation, forKey: "Shake")
ilkayaktas
1

Sie können den folgenden Code ausprobieren:

+ (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"];

}

Über den Link.

HD-Entwickler
quelle
1

Hier ist eine Version mit,

+ (void)animateKeyframesWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewKeyframeAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion

Eingeführt 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];
Richard Stelling
quelle
1

Swift 4.0:

Basierend auf der Top-Antwort, aber einer Verfeinerung der Animation: Dies hat nicht die Sprünge am Anfang und Ende der 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))
Vlad
quelle