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.
- Collision detection; how to detect collisions between the different animated views and boundaries.
- Stasis; we’ll get to know what’s meant by stasis and how to detect it.
- How to avoid memory cycles in your animation code.
Collision Detection
In some cases, after animating your views, you may want to detect the collision between:
- The different animated views (or a subset of them).
- The different animated views and the surrounding boundaries.
To be able to detect collisions, you should:
- Conform to the UICollisionBehaviorDelegate protocol.
- Implement one or more of the functions:
- beganContactForItem, withItem
- endedContactForItem, withItem
- beganContactForItem, withBoundary
- endedContactForItem, withBoundary
- 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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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:
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:
- When did the UIDynamicBehavior start animating views
- When did it end animating views (stasis).
Let’s refactor our code accordingly to display a message when we reach stasis.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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:
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ;]
Pingback: Music is Everything. Songs to the TikTok Videos | free-instagram.com