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.

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

Collision notifications

So far you have added a few views and behaviors then let dynamics take over. In this next step you will look at how to receive notifications when items collide.

Still in ViewController.swift, adopt the UICollisionBehaviorDelegate protocol by updating the class declaration:

class ViewController: UIViewController, UICollisionBehaviorDelegate {

In viewDidLoad, set the view controller as the delegate just after the collision object is initialized, as follows:

collision.collisionDelegate = self

Next, add an implementation for one of the collision behavior delegate methods to the class:

func collisionBehavior(behavior: UICollisionBehavior!, beganContactForItem item: UIDynamicItem!, withBoundaryIdentifier identifier: NSCopying!, atPoint p: CGPoint) {
  println("Boundary contact occurred - \(identifier)")
}

This delegate method is called when a collision occurs. It prints out a log message to the console. In order to avoid cluttering up your console log with lots of messages, feel free to remove the collision.action logging you added in the previous section.

Build and run; your objects will interact, and you’ll see the following entries in your console:

Boundary contact occurred - barrier
Boundary contact occurred - barrier
Boundary contact occurred - nil
Boundary contact occurred - nil
Boundary contact occurred - nil
Boundary contact occurred - nil

From the log messages above you can see that the square collides twice with the boundary identifier barrier; this is the invisible boundary you added earlier. The (null) identifier refers to the outer reference view boundary.

These log messages can be fascinating reading (seriously!), but it would be much more fun to provide a visual indication when the item bounces.

Below the line that sends message to the log, add the following:

let collidingView = item as UIView
collidingView.backgroundColor = UIColor.yellowColor()
UIView.animateWithDuration(0.3) {
  collidingView.backgroundColor = UIColor.grayColor()
}

The above code changes the background color of the colliding item to yellow, and then fades it back to gray again.

Build and run to see this effect in action:

YellowCollision

The square will flash yellow each time it hits a boundary.

So far UIKit Dynamics has automatically set the physical properties of your items (such as mass and elasticity) by calculating them based on your item’s bounds. Next up you’ll see how you can control these physical properties yourself by using the UIDynamicItemBehavior class.

Configuring item properties

Within viewDidLoad, add the following to the end of the method:

let itemBehaviour = UIDynamicItemBehavior(items: [square])
itemBehaviour.elasticity = 0.6
animator.addBehavior(itemBehaviour)

The above code creates an item behavior, associates it with the square, and then adds the behavior object to the animator. The elasticity property controls the bounciness of the item; a value of 1.0 represents a completely elastic collision; that is, where no energy or velocity is lost in a collision. You’ve set the elasticity of your square to 0.6, which means that the square will lose velocity with each bounce.

Build and run your app, and you’ll notice that the square now behaves in a bouncier manner, as below:

PrettyBounce

Note: If you are wondering how I produced the above image with trails that show the previous positions of the square, it was actually very easy! I simply added a block to the action property of one of the behaviors, and every third time the block code was executed, added a new square to the view using the current center and transform from the square. You can see my solution in the spoiler section below.

[spoiler title=”Trails”]

[/spoiler]

Note: If you are wondering how I produced the above image with trails that show the previous positions of the square, it was actually very easy! I simply added a block to the action property of one of the behaviors, and every third time the block code was executed, added a new square to the view using the current center and transform from the square. You can see my solution in the spoiler section below.

[spoiler title=”Trails”]

var updateCount = 0
collision.action = {
  if (updateCount % 3 == 0) {
    let outline = UIView(frame: square.bounds)
    outline.transform = square.transform
    outline.center = square.center

    outline.alpha = 0.5
    outline.backgroundColor = UIColor.clearColor()
    outline.layer.borderColor = square.layer.presentationLayer().backgroundColor
    outline.layer.borderWidth = 1.0
    self.view.addSubview(outline)
  }

    ++updateCount
}

[/spoiler]

var updateCount = 0
collision.action = {
  if (updateCount % 3 == 0) {
    let outline = UIView(frame: square.bounds)
    outline.transform = square.transform
    outline.center = square.center

    outline.alpha = 0.5
    outline.backgroundColor = UIColor.clearColor()
    outline.layer.borderColor = square.layer.presentationLayer().backgroundColor
    outline.layer.borderWidth = 1.0
    self.view.addSubview(outline)
  }

    ++updateCount
}

In the above code you only changed the item’s elasticity; however, the item’s behavior class has a number of other properties that can be manipulated in code. They are as follows:

  • elasticity – determines how ‘elastic’ collisions will be, i.e. how bouncy or ‘rubbery’ the item behaves in collisions.
  • friction – determines the amount of resistance to movement when sliding along a surface.
  • density – when combined with size, this will give the overall mass of an item. The greater the mass, the harder it is to accelerate or decelerate an object.
  • resistance – determines the amount of resistance to any linear movement. This is in contrast to friction, which only applies to sliding movements.
  • angularResistance – determines the amount of resistance to any rotational movement.
  • allowsRotation – this is an interesting one that doesn’t model any real-world physics property. With this property set to NO the object will not rotate at all, regardless of any rotational forces that occur.

Adding behaviors dynamically

In its current state, your app sets up all of the behaviors of the system, then lets dynamics handle the physics of the system until all items come to rest. In this next step, you’ll see how behaviors can be added and removed dynamically.

Open ViewController.swift and add the following property, above viewDidLoad:

var firstContact = false

Add the following code to the end of the collision delegate method collisionBehavior(behavior:beganContactForItem:withBoundaryIdentifier:atPoint:)

if (!firstContact) {
  firstContact = true

  let square = UIView(frame: CGRect(x: 30, y: 0, width: 100, height: 100))
  square.backgroundColor = UIColor.grayColor()
  view.addSubview(square)

  collision.addItem(square)
  gravity.addItem(square)

  let attach = UIAttachmentBehavior(item: collidingView, attachedToItem:square)
  animator.addBehavior(attach)
}

The above code detects the initial contact between the barrier and the square, creates a second square and adds it to the collision and gravity behaviors. In addition, you set up an attachment behavior to create the effect of attaching a pair of objects with a virtual spring.

Build and run your app; you should see a new square appear when the original square hits the barrier, as shown below:

Attachment

While there appears to be a connection between the two squares, you can’t actually see the connection as a line or spring since nothing has been drawn on the screen to represent it.

James Frost

Contributors

James Frost

Author

Over 300 content creators. Join our team.