2009-10-27 2 views
44

При неудаче входа в систему я бы предпочел не показывать предупреждение, это слишком мимолетно. Отображение предупреждения, а затем отображение текста где-то на экране входа в систему похоже на дублирование.Встряхните визуальный эффект на iPhone (НЕ встряхивая устройство)

Поэтому я хотел бы, чтобы он графически встряхнул мое окно входа, когда пользователь вводит неверный идентификатор пользователя и пароль, как это делает экран входа в Mac.

Кто-нибудь знает, есть ли способ снять это или предложить какие-либо предложения для другого эффекта, который я мог бы использовать?

+0

5 лет спустя http://stackoverflow.com/questions/24356051 – Fattie

ответ

97

Я думаю, что это более эффективное решение:

Swift:

let anim = CAKeyframeAnimation(keyPath:"transform") 
anim.values = [ 
    NSValue(CATransform3D:CATransform3DMakeTranslation(-5, 0, 0)), 
    NSValue(CATransform3D:CATransform3DMakeTranslation(5, 0, 0)) 
] 
anim.autoreverses = true 
anim.repeatCount = 2 
anim.duration = 7/100 

viewToShake.layer.addAnimation(anim, forKey:nil) 

Obj-C: создается

CAKeyframeAnimation * anim = [ CAKeyframeAnimation animationWithKeyPath:@"transform" ] ; 
anim.values = @[ 
    [ NSValue valueWithCATransform3D:CATransform3DMakeTranslation(-5.0f, 0.0f, 0.0f) ], 
    [ NSValue valueWithCATransform3D:CATransform3DMakeTranslation(5.0f, 0.0f, 0.0f) ] 
] ; 
anim.autoreverses = YES ; 
anim.repeatCount = 2.0f ; 
anim.duration = 0.07f ; 

[ viewToShake.layer addAnimation:anim forKey:nil ] ; 

только один объект анимации и все это выполняется на уровне CoreAnimation.

+0

Perfect BUT на 2014 год, см. ... http://stackoverflow.com/questions/24356051 !! – Fattie

+1

Также см. Swift: http://stackoverflow.com/a/27988876/1438339 –

6

Простое изменение координаты X для свойства центра вашего вида может сделать трюк. Если вы еще не сделали основной анимации, пока она довольно прямолинейна.

Сначала начните анимацию вправо, затем слушайте ее, чтобы закончить, а затем вернитесь влево и так далее. Получение времени вниз, чтобы он «чувствовал себя правильно», может занять некоторое время.

- (void)animationFinishCallback:(NSString *)animationID finished:(BOOL)finished context:(void *)context 
{ 
    if ([animationID isEqualToString:@"MoveRight"]) { 
    [UIView beginAnimations:@"MoveLeft" context:NULL]; 
    [UIView setAnimationDuration:1.0]; 
    [UIView setAnimationDelay: UIViewAnimationCurveEaseIn]; 
    [UIView setAnimationDelegate:self]; 
    [UIView setAnimationDidStopSelector:@selector(animationFinishCallback:finished:context:)]; 

    myView.center = CGRectMake(newX, newY); 
    [UIView commitAnimations]; 
    } 
} 
+4

Вы также можете создать анимацию и применить ее к 'CALayer' представления. Это позволит вам использовать, например, 'CAKeyframeAnimation', который позволит вам точно настроить анимацию. Для такой анимации вам, вероятно, понадобится такой уровень гибкости. – Alex

+0

см. Мой ответ ниже - он использует одиночную анимацию CoreAnimation, которая автоматически удаляется, когда анимация завершается ... нет необходимости в обратных вызовах или нескольких блоках анимации – nielsbot

40

Я видел некоторые качаний анимации и изменил его встряхнуть вид т пикселей в вертикальном положении и downleft:

- (void)earthquake:(UIView*)itemView 
{ 
    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:5]; 
    [UIView setAnimationDuration:0.07]; 
    [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; 
    } 
} 
59

Использование iOS 4+ блочных анимаций UIKit (и свободно основанных на ответе jayccrown):

- (void)shakeView:(UIView *)viewToShake 
{ 
    CGFloat t = 2.0; 
    CGAffineTransform translateRight = CGAffineTransformTranslate(CGAffineTransformIdentity, t, 0.0); 
    CGAffineTransform translateLeft = CGAffineTransformTranslate(CGAffineTransformIdentity, -t, 0.0); 

    viewToShake.transform = translateLeft; 

    [UIView animateWithDuration:0.07 delay:0.0 options:UIViewAnimationOptionAutoreverse|UIViewAnimationOptionRepeat animations:^{ 
     [UIView setAnimationRepeatCount:2.0]; 
     viewToShake.transform = translateRight; 
    } completion:^(BOOL finished) { 
     if (finished) { 
      [UIView animateWithDuration:0.05 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ 
       viewToShake.transform = CGAffineTransformIdentity; 
      } completion:NULL]; 
     } 
    }]; 
} 
+4

Это должен быть главный ответ на вопрос, так как все вышеприведенные ответы будут не работает должным образом (без изменений или опускания параметра вида) при включенном ARC. Используйте блоки! –

+0

yup, это лучший ответ, вы можете не захотеть проверить «закончен», иначе вы можете получить нечетное преобразование – slf

+2

Вы можете сделать это только для одной анимации автообновления - нет необходимости в блоках завершения. (Я опубликовал код ниже) – nielsbot

4

Этот фрагмент категории UIView работал для меня. Используется 3 CABasingAnimations, применяемых к слою view.

#import <UIKit/UIKit.h> 
#import <QuartzCore/QuartzCore.h> 

#define Y_OFFSET 2.0f 
#define X_OFFSET 2.0f 
#define ANGLE_OFFSET (M_PI_4*0.1f) 

@interface UIView (shakeAnimation) 

-(BOOL)isShakeAnimationRunning; 
-(void)startShakeAnimation; 
-(void)stopShakeAnimation; 

@end 



@implementation UIView (shakeAnimation) 

-(BOOL)isShakeAnimationRunning{ 
    return [self.layer animationForKey:@"shake_rotation"] != nil; 
} 

-(void)startShakeAnimation{ 
    CFTimeInterval offset=(double)arc4random()/(double)RAND_MAX; 
    self.transform=CGAffineTransformRotate(self.transform, -ANGLE_OFFSET*0.5); 
    self.transform=CGAffineTransformTranslate(self.transform, -X_OFFSET*0.5f, -Y_OFFSET*0.5f); 

    CABasicAnimation *tAnim=[CABasicAnimation animationWithKeyPath:@"position.x"]; 
    tAnim.repeatCount=HUGE_VALF; 
    tAnim.byValue=[NSNumber numberWithFloat:X_OFFSET]; 
    tAnim.duration=0.07f; 
    tAnim.autoreverses=YES; 
    tAnim.timeOffset=offset; 
    [self.layer addAnimation:tAnim forKey:@"shake_translation_x"]; 

    CABasicAnimation *tyAnim=[CABasicAnimation animationWithKeyPath:@"position.y"]; 
    tyAnim.repeatCount=HUGE_VALF; 
    tyAnim.byValue=[NSNumber numberWithFloat:Y_OFFSET]; 
    tyAnim.duration=0.06f; 
    tyAnim.autoreverses=YES; 
    tyAnim.timeOffset=offset; 
    [self.layer addAnimation:tyAnim forKey:@"shake_translation_y"]; 

    CABasicAnimation *rAnim=[CABasicAnimation animationWithKeyPath:@"transform.rotation"]; 
    rAnim.repeatCount=HUGE_VALF; 
    rAnim.byValue=[NSNumber numberWithFloat:ANGLE_OFFSET]; 
    rAnim.duration=0.15f; 
    rAnim.autoreverses=YES; 
    rAnim.timeOffset=offset; 
    [self.layer addAnimation:rAnim forKey:@"shake_rotation"]; 
} 
-(void)stopShakeAnimation{ 
    [self.layer removeAnimationForKey:@"shake_translation_x"]; 
    [self.layer removeAnimationForKey:@"shake_translation_y"]; 
    [self.layer removeAnimationForKey:@"shake_rotation"]; 
    [UIView animateWithDuration:0.2f animations:^{ 
     self.transform=CGAffineTransformRotate(self.transform, ANGLE_OFFSET*0.5); 
     self.transform=CGAffineTransformTranslate(self.transform, X_OFFSET*0.5, Y_OFFSET*0.5f); 
    }]; 
} 

@end 

Надеется, что это кто-то helpes :)

1

Я знаю, что этот вопрос уже ответило, но так как я уже реализовал что-то вроде этого раньше, я чувствую, что это не помешает добавить:

CAKeyframeAnimation *shakeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"]; 
NSArray *transformValues = [NSArray arrayWithObjects: 
         [NSNumber numberWithFloat:((M_PI)/64)], 
         [NSNumber numberWithFloat:(-((M_PI)/64))], 
         [NSNumber numberWithFloat:((M_PI)/64)], 
         [NSNumber numberWithFloat:(-((M_PI)/64))], 
         [NSNumber numberWithFloat:((M_PI)/64)], 
         [NSNumber numberWithFloat:(-((M_PI)/64))], 
         [NSNumber numberWithFloat:0],         
         nil]; 

[shakeAnimation setValues:transformValues]; 

NSArray *times = [NSArray arrayWithObjects: 
        [NSNumber numberWithFloat:0.14f], 
        [NSNumber numberWithFloat:0.28f], 
        [NSNumber numberWithFloat:0.42f], 
        [NSNumber numberWithFloat:0.57f], 
        [NSNumber numberWithFloat:0.71f], 
        [NSNumber numberWithFloat:0.85f], 
        [NSNumber numberWithFloat:1.0f], 
        nil]; 

[shakeAnimation setKeyTimes:times]; 

shakeAnimation.fillMode = kCAFillModeForwards; 
shakeAnimation.removedOnCompletion = NO; 
shakeAnimation.duration = 0.6f; 

[self.viewToShake.layer addAnimation:shakeAnimation forKey:@"anim"]; 

Кроме того, так как вы хотите, встряхивая, чтобы указать, что пользователь не смог войти в систему, вы можете также добавить эту анимацию, оттенки экран красным, а дрожит экран:

//Put this in the header (.h) 
@property (nonatomic, strong) UIView *redView; 

//Put this in the implementation (.m) 
@synthesize redView; 

//Put this in viewDidLoad 
self.redView = [[UIView alloc] initWithFrame:self.view.frame]; 
self.redView.layer.opacity = 0.0f; 
self.redView.layer.backgroundColor = [[UIColor redColor] CGColor]; 

//Put this wherever you check if the login failed 
CAKeyframeAnimation *redTint = [CAKeyframeAnimation animationWithKeyPath:@"opacity"]; 
NSArray *transformValues = [NSArray arrayWithObjects: 
          [NSNumber numberWithFloat:0.2f], 
          [NSNumber numberWithFloat:0.0f],         
          nil]; 

[redTint setValues:transformValues]; 

NSArray *times = [NSArray arrayWithObjects: 
        [NSNumber numberWithFloat:0.5f], 
        [NSNumber numberWithFloat:1.0f], 
        nil]; 

[redTint setKeyTimes:times]; 

redTint.fillMode = kCAFillModeForwards; 
redTint.removedOnCompletion = NO; 
redTint.duration = 0.6f; 

[self.redView.layer addAnimation:shakeAnimation forKey:@"anim"]; 

Надеюсь, что это поможет!

0

Использование автоматической компоновки, я приспособил ответ Криса Майлза, но анимированные NSLayoutConstraints так:

NSLayoutConstraint *left = ... 
NSLayoutConstraint *right = ... 

[UIView animateWithDuration:0.08 delay:0.0 options:UIViewAnimationOptionAutoreverse|UIViewAnimationOptionRepeat animations:^{ 
    [UIView setAnimationRepeatCount:3]; 
    left.constant = 15.0; 
    right.constant = 25.0; 
    [self.view layoutIfNeeded]; 
} completion:^(BOOL finished) { 
    if (finished) { 
     [UIView animateWithDuration:0.08 animations:^{ 
      left.constant = 20.0; 
      right.constant = 20.0; 
      [self.view layoutIfNeeded]; 
     } completion:NULL]; 
    } 
}]; 
2

В прошивкой 7.0 или более поздней версии, UIKit ключевой кадр анимации доступен.

[UIView animateKeyframesWithDuration:0.5 delay:0.0 options:0 animations:^{ 
    [UIView setAnimationCurve:UIViewAnimationCurveLinear]; 

    NSInteger repeatCount = 8; 
    NSTimeInterval duration = 1.0/(NSTimeInterval)repeatCount; 

    for (NSInteger i = 0; i < repeatCount; i++) { 
     [UIView addKeyframeWithRelativeStartTime:i * duration relativeDuration:duration animations:^{ 
      CGFloat dx = 5.0; 
      if (i == repeatCount - 1) { 
       viewToShake.transform = CGAffineTransformIdentity; 
      } else if (i % 2) { 
       viewToShake.transform = CGAffineTransformTranslate(CGAffineTransformIdentity, -dx, 0.0); 
      } else { 
       viewToShake.transform = CGAffineTransformTranslate(CGAffineTransformIdentity, +dx, 0.0); 
      } 
     }]; 
    } 
} completion:completion]; 
0

Решение, которое я использовал для ограничений, которые я установил в своем раскадровке. Без использования animateWithDuration.

@IBOutlet var balloonHorizontalConstraint: NSLayoutConstraint! 

NSTimer.scheduledTimerWithTimeInterval(0.04, target: self, selector: "animateBalloon", userInfo: nil, repeats: true) 

func animateBalloon() { 
    switch balloonHorizontalConstraint.constant { 
    case -46: 
     balloonHorizontalConstraint.constant = -50 
    default: 
     balloonHorizontalConstraint.constant = -46 
    } 
} 

В моем случае анимация просто продолжал идти, но я сую свой ViewController после продолжительностью в несколько секунд, это останавливает свой таймер Aswell.

Смежные вопросы