UIKit Dynamics – Part 2 (Collision Detection, Stasis & Avoiding Memory Cycles)

Note, this is part 2 in the UIKit Dynamics tutorials. In part 1 we talked about getting started using UIKit Dynamics. If you’re not familiar with how to animate views using UIDynamicAnimator and how to make use of UIDynamicBehavior subclasses, it’s crucial to take a look first at part 1.
In part 2, we’ll be discussing 3 more topics related to UIKit Dynamics.

  1. Collision detection; how to detect collisions between the different animated views and boundaries.
  2. Stasis; we’ll get to know what’s meant by stasis and how to detect it.
  3. How to avoid memory cycles in your animation code.

collision_cover

Collision Detection

In some cases, after animating your views, you may want to detect the collision between:

  1. The different animated views (or a subset of them).
  2. The different animated views and the surrounding boundaries.

To be able to detect collisions, you should:

  1. Conform to the UICollisionBehaviorDelegate protocol.
  2. Implement one or more of the functions:
    1. beganContactForItem, withItem
    2. endedContactForItem, withItem
    3. beganContactForItem, withBoundary
    4. endedContactForItem, withBoundary
  3. Set the view controller as the collisionDelegate for your collision behavior.

Let’s add this functionality to our demo app, assume we want to display a message only when our moving squares hit the middle black view at the middle of the screen.
Here’s what our code will look like:


// 1. Conform to UICillisionBehaviorDelegate
AnimationViewController: UIViewController, UICollisionBehaviorDelegate
// 2. Handling collision
// This function will be called with every and each collision starts between all the views added to our collision behavior.
func collisionBehavior(behavior: UICollisionBehavior, beganContactForItem item1: UIDynamicItem, withItem item2: UIDynamicItem, atPoint p: CGPoint){
// Wer're only interested in collisions with the black circle (snappingCircle).
if (item1 as? UIView)?.tag == snappingCirlceTag || (item2 as? UIView)?.tag == snappingCirlceTag {
displayCollisionMessage(message)
}
}
// 3. Setting self as the collision bahavior delegate
lazy var squareBehavior: SquareBehavior = {
let lazySquareBehavior = SquareBehavior(settings: self.animationSettings)
self.animator.addBehavior(lazySquareBehavior)
// Here..
lazySquareBehavior.collider.collisionDelegate = self
return lazySquareBehavior
}()

And here’s what it looks like during animation, note the message that appears after collision:

UIDynamics_collision

Stasis

Stasis is the name of the state in which all views are not animating anymore. All the behaviors inside the animator have nothing to do with the views, the universe has stopped moving!

Just like the UICollisionBehaviorDelegate, your view controller can conform to UIDynamicAnimatorDelegate which can tell you 2 things:

  1. When did the UIDynamicBehavior start animating views
  2. When did it end animating views (stasis).

Let’s refactor our code accordingly to display a message when we reach stasis.


// 1. Let's add UIDynamicAnimatorDelegate to our implemented protocols.
class AnimationViewController: UIViewController, UICollisionBehaviorDelegate, UIDynamicAnimatorDelegate
// 2. Set self as the UIDynamicAnimatorDelegate
lazy var animator: UIDynamicAnimator = {
let lazyDynamicAnimator = UIDynamicAnimator(referenceView: self.animationView)
lazyDynamicAnimator.delegate = self
return lazyDynamicAnimator
}()
// 3. Hanlde stasis, by displaying a message.
func dynamicAnimatorDidPause(animator: UIDynamicAnimator){
self.displayMessage("Stasis! The universe stopped moving.")
}

As you can see, after the 2 squares stop animating, the stasis message will appear:

UIDynamics_stasis

Avoiding Memory Cycles

When you pay more attention to the code that creates a new push behavior that poses an instantaneous push to shoot squares from right and left:


pushBehavior.action = {
pushBehavior.dynamicAnimator!.removeBehavior(pushBehavior)
}

You’ll notice that push behavior’s action property is referencing the push behavior itself. Well, this is bad, as it results in a memory cycle that will live in the memory forever.
The solution is so simple, you just tell that the action’s closure doesn’t own the push behavior. How is that?


pushBehavior.action = { [unowned pushBehavior] in
pushBehavior.dynamicAnimator!.removeBehavior(pushBehavior)
}

The pushBehavior inside the closure is unowned, meaning; don’t capture it and keep it in memory! I promise you Mr. Closure that it’ll be there when you want to use it.

Full Demo App

The demo app is available on Github, feel free clone, try & customize it.

https://github.com/ahmed-abdurrahman/UIDynamicsDemo

Comments, notes & questions

That’s the end to our 2 part tutorial (part 1 link), hope you found it useful. It’s really my pleasure to answer your questions and receive your notes in the comments section below.

See you next time, stay safe, away from collisions ;]

One thought on “UIKit Dynamics – Part 2 (Collision Detection, Stasis & Avoiding Memory Cycles)

  1. Pingback: Music is Everything. Songs to the TikTok Videos | free-instagram.com

Leave a comment