Episode #137

Fun with UIKit Dynamics

15 minutes
Published on September 18, 2014

This video is only available to subscribers. Get access to this video and 572 others.

In this episode we explore the powerful UIKit Dynamics that was introduced with iOS 7. With Dynamics you can simulate real world interactions between your views. We'll go over the basics of collision, gravity, rigid bounds, and leave off with an example of why you shouldn't use it to make games.

Episode Links

Setting up the "World"

First we need an animator and an initial gravity behavior:

@interface ViewController ()
@property (nonatomic, strong) UIView *square;
@property (nonatomic, strong) UIDynamicAnimator *animator;
@property (nonatomic, strong) UIGravityBehavior *gravity;

We initialize these in viewDidLoad:

    UIView *square = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
    square.backgroundColor = [UIColor colorWithRed:0.311 green:0.101 blue:0.311 alpha:1.000];
    square.center = self.view.center;
    square.layer.shadowColor = [UIColor blackColor].CGColor;
    square.layer.shadowOffset = CGSizeMake(1, 1);
    square.layer.shadowOpacity = 0.5;
    square.transform = CGAffineTransformMakeRotation(M_PI  / 4.1);
    self.square = square;
    [self.view addSubview:square];

    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

    self.gravity = [[UIGravityBehavior alloc] initWithItems:@[ square ]];

    [self.animator addBehavior:self.gravity];

Running this we see the square view just falls off the screen. To get our view bounds to act as a platform we need a collision behavior.

Adding in a Collision Behavior

We start off adding a property to hold the behavior. This isn't strictly required, but we might want to tweak it or toggle it later, so it helps to have a reference available to the entire class:

@property (nonatomic, strong) UICollisionBehavior *collision;

Then we initialize it:

    self.collision = [[UICollisionBehavior alloc] initWithItems:@[square]];
    self.collision.translatesReferenceBoundsIntoBoundary = YES;    

    // ...

    [self.animator addBehavior:self.collision];

The items control which subviews interact with each other. To get it to interact with the containing view, you need to set translatesReferenceBoundsIntoBoundary to YES.

Adding a Rigid Barrier

    UIView *barrier = [[UIView alloc] initWithFrame:CGRectMake(0, 200, 130, 10)];
    barrier.backgroundColor = [UIColor redColor];
    [self.view addSubview:barrier];

We could add this to the collision behavior, but a collision would cause the barrier to move as well. Instead we add it as a boundary:

    [self.collision addBoundaryWithIdentifier:@"barrier"
                                      forPath:[UIBezierPath bezierPathWithRect:barrier.frame]];

Setting Friction, Elasticity, or Angular Resistance

We can control the physical reaction properties of a given dynamic item by giving it a dynamic item behavior:

    UIDynamicItemBehavior *behavior = [[UIDynamicItemBehavior alloc] initWithItems:@[square]];
    behavior.elasticity = 0.5;
    behavior.friction = 0.2;
    behavior.angularResistance = 0.5;

    // ...

    [self.animator addBehavior:behavior];

Providing Impulse Forces

We can add a bit of interactivity by adding an impulse force on our view when the user taps:

    [self bump];

    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(bump)];
    [self.view addGestureRecognizer:tap];

The bump method simply adds a dynamic behavior that has a linear velocity added to it:

- (void)bump {
    UIDynamicItemBehavior *bump = [[UIDynamicItemBehavior alloc] initWithItems:@[self.square]];
    [bump addLinearVelocity:CGPointMake(600, -1200) forItem:self.square];
    [self.animator addBehavior:bump];