UIKit Dynamics Tutorial

Learn how to make your user interfaces in iOS 7 feel realistic with this UIKit Dynamics tutorial! By Colin Eberhardt.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Handling collisions

Next up you’ll add an immovable barrier that the falling square will collide and interact with.
Insert the following code to viewDidLoad just after the lines that add the square to the view:

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

Build and run your app; you’ll see a red “barrier” extending halfway across the screen. However, it turns out the barrier isn’t that effective as the square falls straight through the barrier:

BadBarrier

That’s not quite the effect you were looking for, but it does provide an important reminder: dynamics only affect views that have been associated with behaviors.

Time for a quick diagram:

DynamicClasses

UIDynamicAnimator is associated with a reference view that provides the coordinate system. You then add one or more behaviors that exert forces on the items they are associated with. Most behaviors can be associated with multiple items, and each item can be associated with multiple behaviors. The above diagram shows the current behaviors and their associations within your app.

Neither of the behaviors in your current code is “aware” of the barrier, so as far as the underling dynamics engine is concerned, the barrier doesn’t even exist.

Making objects respond to collisions

To make the square collide with the barrier, find the line that initializes the collision behavior and replace it with the following:

_collision = [[UICollisionBehavior alloc] initWithItems:@[square, barrier]];

The collision object needs to know about every view it should interact with; therefore adding the barrier to the list of items allows the collision object to act upon the barrier as well.

Build and run your app; the two objects collide and interact, as shown in the following screenshot:

GoodBarrier

The collision behavior forms a “boundary” around each item that it’s associated with; this changes them from objects that can pass through each other into something more solid.

Updating the earlier diagram, you can see that the collision behavior is now associated with both views:

DynamicClasses2

However, there’s still something not quite right with the interaction between the two objects. The barrier is supposed to be immovable, but when the two objects collide in your current configuration the barrier is knocked out of place and starts spinning towards the bottom of the screen.

Even more oddly, the barrier bounces off the bottom of the screen and doesn’t quite settle down like the square – this makes sense because the gravity behavior doesn’t interact with the barrier. This also explains why the barrier doesn’t move until the square collides with it.

Looks like you need a different approach to the problem. Since the barrier view is immovable, there isn’t any need to for the dynamics engine to be aware of its existence. But how will the collision be detected?

Invisible boundaries and collisions

Change the collision behavior initialization back to its original form so that it’s only aware of the square:

_collision = [[UICollisionBehavior alloc] initWithItems:@[square]];

Next, add a boundary as follows:

// add a boundary that coincides with the top edge
CGPoint rightEdge = CGPointMake(barrier.frame.origin.x +
                                barrier.frame.size.width, barrier.frame.origin.y);
[_collision addBoundaryWithIdentifier:@"barrier"
                            fromPoint:barrier.frame.origin
                              toPoint:rightEdge];

The above code adds an invisible boundary that coincides with the top edge of the barrier view. The red barrier remains visible to the user but not to the dynamics engine, while the boundary is visible to the dynamics engine but not the user. As the square falls, it appears to interact with the barrier, but it actually hits the immovable boundary line instead.

Build and run your app to see this in action, as below:

BestBarrier

The square now bounces off the boundary, spins a little, and then continues its journey towards the bottom of the screen where it comes to rest.

By now the power of UIKit Dynamics is becoming rather clear: you can accomplish quite a lot with only a few lines of code. There’s a lot going on under the hood; the next section shows you some of the details of how the dynamic engine interacts with the objects in your app.

Behind the scenes of collisions

Each dynamic behavior has an action property where you supply a block to be executed with every step of the animation. Add the following code to viewDidLoad:

_collision.action =  ^{
    NSLog(@"%@, %@", 
          NSStringFromCGAffineTransform(square.transform), 
          NSStringFromCGPoint(square.center));
};

The above code logs the center and transform properties for the falling square. Build and run your app, and you’ll see these log messages in the Xcode console window.

For the first ~400 milliseconds you should see log messages like the following:

2013-07-26 08:21:58.698 DynamicsPlayground[17719:a0b] [1, 0, 0, 1, 0, 0], {150, 236}
2013-07-26 08:21:58.715 DynamicsPlayground[17719:a0b] [1, 0, 0, 1, 0, 0], {150, 243}
2013-07-26 08:21:58.732 DynamicsPlayground[17719:a0b] [1, 0, 0, 1, 0, 0], {150, 250}

Here you can see that the dynamics engine is changing the center of the square — that is, its frame— in each animation step.

As soon as the square hits the barrier, it starts to spin, which results in log messages like the following:

2013-07-26 08:21:59.182 DynamicsPlayground[17719:a0b] [0.10679234, 0.99428135, -0.99428135, 0.10679234, 0, 0], {198, 325}
2013-07-26 08:21:59.198 DynamicsPlayground[17719:a0b] [0.051373702, 0.99867952, -0.99867952, 0.051373702, 0, 0], {199, 331}
2013-07-26 08:21:59.215 DynamicsPlayground[17719:a0b] [-0.0040036771, 0.99999201, -0.99999201, -0.0040036771, 0, 0], {201, 338}

Here you can see that the dynamics engine is using a combination of a transform and a frame offset to position the view according to the underlying physics model.

While the exact values that dynamics applies to these properties are probably of little interest, it’s important to know that they are being applied. As a result, if you programmatically change the frame or transform properties of your object, you can expect that these values will be overwritten. This means that you can’t use a transform to scale your object while it is under the control of dynamics.

The method signatures for the dynamic behaviors use the term items rather than views. The only requirement to apply dynamic behavior to an object is that it adopts the UIDynamicItem protocol, as so:

@protocol UIDynamicItem <NSObject>

@property (nonatomic, readwrite) CGPoint center;
@property (nonatomic, readonly) CGRect bounds;
@property (nonatomic, readwrite) CGAffineTransform transform;

@end

The UIDynamicItem protocol gives dynamics read and write access to the center and transform properties, allowing it to move the items based on its internal computations. It also has read access to bounds, which it uses to determine the size of the item. This allows it to create collision boundaries around the perimeter of the item as well as compute the item’s mass when forces are applied.

This protocol means that dynamics is not tightly coupled to UIView; indeed there is another UIKit class that adopts this protocol – UICollectionViewLayoutAttributes. This allows dynamics to animate items within collection views.

Colin Eberhardt

Contributors

Colin Eberhardt

Author

Over 300 content creators. Join our team.