2009-10-27 5 views
44

W przypadku niepowodzenia logowania wolałbym nie pokazywać alertu, jest on zbyt ulotny. Pokazywanie alertu, a następnie pokazywanie go gdzieś na ekranie logowania wydaje się być duplikatem.Wstrząśnij efekt wizualny na iPhonie (NIE wstrząsając urządzeniem)

Chciałbym więc graficznie wstrząsnąć moim widokiem logowania, gdy użytkownik wprowadzi zły identyfikator użytkownika i hasło, tak jak robi to ekran logowania do Mac.

Ktoś wie, czy jest jakiś sposób, aby to zrobić, czy masz jakieś sugestie dotyczące innego efektu, którego mógłbym użyć?

+0

5 lat później .. http://stackoverflow.com/questions/24356051 – Fattie

Odpowiedz

97

myślę, że jest to bardziej efektywne rozwiązanie:

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: jest tworzony

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

tylko jeden obiekt animacji i to wszystko jest wykonywane na poziomie CoreAnimation.

+0

Perfect ALE na rok 2014, zobacz ... http://stackoverflow.com/questions/24356051 !! – Fattie

+1

Zobacz także Swift: http://stackoverflow.com/a/27988876/1438339 –

6

Po prostu zmiana współrzędnej X środkowej właściwości widoku może załatwić sprawę. Jeśli nie wykonałeś żadnej głównej animacji, zanim będzie to całkiem proste.

Najpierw uruchom animację w prawo, a następnie słuchaj, aż się skończy, a następnie wróć w lewo i tak dalej. Osiągnięcie limitu czasu, aby "dobrze się czuł", może trochę potrwać.

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

Można również utworzyć animację i zastosować ją do widoku 'CALayer'. Pozwoli to na użycie np. 'CAKeyframeAnimation', która pozwoli ci dopracować animację. W przypadku takiej animacji prawdopodobnie będziesz potrzebował takiego poziomu elastyczności. – Alex

+0

zobacz moją odpowiedź poniżej - wykorzystuje pojedynczą animację CoreAnimation, która jest automatycznie usuwana po zakończeniu animacji ... nie ma potrzeby wywoływania zwrotnego ani animowania bloków – nielsbot

40

widziałem jakiś falowy animacji i zmienił go wstrząsnąć Wyświetl T pikseli pionowo i 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

Używanie animacji UIKit opartych na blokach iOS 4+ (luźno opartych na odpowiedzi 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

To powinna być najlepsza odpowiedź na to pytanie, ponieważ wszystkie powyższe odpowiedzi będą nie działa poprawnie (bez modyfikacji lub pominięcia parametru widoku) z włączonym ARC. Użyj bloków! –

+0

tak, to jest lepsza odpowiedź, możesz nie chcieć sprawdzić 'zakończone' chociaż, w przeciwnym razie możesz skończyć z nieparzystą transformacją – slf

+2

Możesz to zrobić w tylko jednej animacji autoreversing - nie ma potrzeby ukończenia bloków. (Napisałem kod poniżej) – nielsbot

4

Ten fragment kodu UIView pracował dla mnie. Używa 3 CABasingAnimations zastosowanych do warstwy widoku.

#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 

Hope it helpes ktoś :)

1

Znam pytanie jest już odpowiedź, ale ponieważ już wdrożone coś takiego wcześniej, czuję, że nie zaszkodzi ją dodać:

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

ponadto, ponieważ chcesz, kręcąc się wskazywać, że użytkownik nie zalogował się, można także rozważyć dodanie tej animacji, które odcienie czerwonego ekranu, gdy ekran trzęsie:

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

Mam nadzieję, że to pomoże!

0

Korzystanie Auto układ, ja dostosowany odpowiedź Chris Milesa, ale animowane NSLayoutConstraints takiego:

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

w iOS 7.0 lub nowszy, UIKit animacji klatka kluczowa jest dostępna.

[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

Rozwiązanie, którego użyłem do wiązania, które ustawiam w moim scenopisie. Bez użycia 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 
    } 
} 

W moim przypadku animacja po prostu ciągle na dzieje, ale pop mój viewcontroller po trwających kilka sekund, to zatrzymuje timer również.