UIView 抖动动画

我试图使一个 UIView 震动时,一个按钮被按下。

我正在修改我在 http://www.cimgf.com/2008/02/27/core-animation-tutorial-window-shake-effect/上找到的代码。

然而,通过尝试修改以下代码来抖动 UIView,它并不起作用:

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


}
48166 次浏览

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. I hope that helps. Let me know if you have any questions.

---


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

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

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


}

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.

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.

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

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

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

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

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

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

@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 is a UIView extension providing an awesome shake animation: https://gist.github.com/mourad-brahim/cf0bfe9bec5f33a6ea66

A Swift5 update is provided on the comments.

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