Joe Blow Joe Blow - 1 year ago 56
iOS Question

Shake a UIView ... BUT using UIKit Dynamics

There are many situations where you need to "shake" a UIView.

(For example, "draw child user's attention to a control", "connection is slow", "user enters bad input," and so on.)

Would it be possible to do this using UIKit Dynamics?

So you'd have to ..

  • take the view, say at 0,0

  • add a spring concept

  • give it a nudge, say to the "left"

  • it should swing back and fore on the spring, ultimately settling again to 0,0

Is this possible? I couldn't find an example in the Apple demos. Cheers

Please note that as Niels astutely explains below, a spring is not necessarily the "physics feel" you want for some of these situations: in other situations it may be perfect. As far as I know, all physics in iOS's own apps (eg Messages etc) now uses UIKit Dynamics, so for me it's worth having a handle on "UIView bouncing on a spring".

Just to be clear, of course you can do something "similar", just with an animation. Example...

But that simply doesn't have the same "physics feel" as the rest of iOS, now.

[CATransaction begin];

CAKeyframeAnimation * anim =
[CAKeyframeAnimation animationWithKeyPath:@"transform"];

anim.values = @[
[NSValue valueWithCATransform3D:
CATransform3DMakeTranslation(-4.0f, 0.0f, 0.0f) ],
[NSValue valueWithCATransform3D:
CATransform3DMakeTranslation(4.0f, 0.0f, 0.0f) ]

anim.autoreverses = YES;
anim.repeatCount = 1.0f;
anim.duration = 0.1f;
[CATransaction setCompletionBlock:^{}];
[self.layer addAnimation:anim forKey:nil];
[CATransaction commit];

Rob Rob
Answer Source

If you want to use UIKit Dynamics, you can:

  • First, define a governing animator:

    @property (nonatomic, strong) UIDynamicAnimator *animator;

    And instantiate it:

    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
  • Second, add attachment behavior to that animator for view in current location. This will make it spring back when the push is done. You'll have to play around with damping and frequency values.

    UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:viewToShake];
    attachment.damping = 0.5;
    attachment.frequency = 5.0;
    [self.animator addBehavior:attachment];

    These values aren't quite right, but perhaps it's a starting point in your experimentation.

  • Apply push behavior (UIPushBehaviorModeInstantaneous) to perturb it. The attachment behavior will then result in its springing back.

    UIPushBehavior *push = [[UIPushBehavior alloc] initWithItems:@[viewToShake] mode:UIPushBehaviorModeInstantaneous];
    push.pushDirection = CGVectorMake(100, 0);
    [self.animator addBehavior:push];

Personally, I'm not crazy about this particular animation (the damped curve doesn't feel quite right to me). I'd be inclined use block based animation to move it one direction (with UIViewAnimationOptionCurveEaseOut), upon completion initiate another to move it in the opposite direction (with UIViewAnimationOptionCurveEaseInOut), and then upon completion of that, use the usingSpringWithDamping rendition of animateWithDuration to move it back to its original spot. IMHO, that yields a curve that feels more like "shake if wrong" experience.