UIKit Dynamics Tutorial: Getting Started
Learn how to make your user interfaces in iOS feel realistic with this UIKit Dynamics tutorial, updated for Swift! By James Frost.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
UIKit Dynamics Tutorial: Getting Started
25 mins
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:
let barrier = UIView(frame: CGRect(x: 0, y: 300, width: 130, height: 20))
barrier.backgroundColor = UIColor.redColor()
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:
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:
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(items: [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:
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:
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(items: [square])
Immediately after this line, add the following:
// add a boundary that has the same frame as the barrier
collision.addBoundaryWithIdentifier("barrier", forPath: UIBezierPath(rect: barrier.frame))
The above code adds an invisible boundary that has the same frame as 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 instead.
Build and run your app to see this in action, as below:
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 = {
println("\(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:
[1, 0, 0, 1, 0, 0], {150, 236}
[1, 0, 0, 1, 0, 0], {150, 243}
[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:
[0.99797821, 0.063557133, -0.063557133, 0.99797821, 0, 0] {152, 247}
[0.99192101, 0.12685727, -0.12685727, 0.99192101, 0, 0] {154, 244}
[0.97873402, 0.20513339, -0.20513339, 0.97873402, 0, 0] {157, 241}
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 : NSObjectProtocol {
var center: CGPoint { get set }
var bounds: CGRect { get }
var transform: CGAffineTransform { get set }
}
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 isn’t a view but still adopts this protocol: UICollectionViewLayoutAttributes
. This allows dynamics to animate items within collection views.