Chapter 13. UI Dynamics

UI Dynamics allow you to create very nice effects on your UI components, such as gravity and collision detection. Let’s say that you have two buttons on the screen that the user can move around. You could create opposing gravity fields on them so that they repel each other and cannot be dragged into each other. Or, for instance, you could provide a more live UI by creating a turbulence field under all your UI components so that they move around automatically ever so slightly (or through a noise field, as described in Recipe 13.4) even when the user is not interacting with them. All of this is possible with the tools that Apple has given you in UIKit. You don’t have to use any other framework to dig into UI Dynamics.

One of the basic concepts in UI Dynamics is an animator. Animator objects, which are of type UIDynamicAnimator, hold every other effect together and orchestrate all the effects. For instance, if you have collision detection and gravity effects, the animator decides how the pull on an object through gravity will work hand in hand with the collision detection around the edges of your reference view.

Reference views are like canvases where all your animations happen. Effects are added to views and then added to an animator, which itself is placed on a reference view. In other words, the reference view is the canvas and the views on your UI (like buttons, lables, etc.) will have effects.

13.1 Adding a Radial Gravity Field to Your UI

Problem

You want to add a radial gravity field to your UI, with animations.

Solution

Use the radialGravityFieldWithPosition(_:) class method of UIFieldBehavior and add this behavior to a dynamic animator of type UIDynamicAnimator.

Discussion

A typical gravity behavior pulls items in a direction. A radial gravity field has a center and a region in which everything is drawn to the center, just like gravity on earth, whereby everything is pulled toward the core of this sphere.

For this recipe, I designed a UI like Figure 13-1. The gravity is at the center of the main view and the orange view is affected by it.

Figure 13-1. A main view and another view that is an orange square

The gravity field here is not linear. I would also like this gravity field to repel the orange view, instead of pulling it toward the core of gravity. Then I’d like the user to be able to pan this orange view around the screen and release it to see how the gravity affects the view at that point in time (think about pan gesture recognizers).

Let’s have a single-view app that has no navigation bar and then go into IB and add a simple colorful view to your main view. I’ve created mine, colored it orange(ish), and have linked it to my view controller under the name orangeView (see Figure 13-2).

Figure 13-2. My view is added on top of the view controller’s view and hooked to the view controller’s code

Then from the object library, find a pan gesture recognizer (see Figure 13-3) and drop it onto your orange view so that it gets associated with that view. Find the pan gesture recognizer by typing its name into the object library’s search field.

Figure 13-3. Getting the pan gesture recognizer

Then associate the pan gesture recognizer’s code to a method in your code called panning(_:). So now your view controller’s header should look like this:

import UIKit
import SharedCode

class ViewController: UIViewController {

  @IBOutlet var orangeView: UIView!

  ...
Note

Whenever I write a piece of code that I want to share between various projects, I put it inside a framework that I’ve written called SharedCode. You can find this framework in the GitHub repo of this book. In this example, I’ve extended CGSize so that I can find the CGPoint at the center of CGSize like so:

import Foundation

extension CGSize{

  public var center: CGPoint{
    return CGPoint(x: self.width / 2.0, y: self.height / 2.0)
  }

}

Then in the vc, create your animator, specifying this view as the reference view:

  lazy var animator: UIDynamicAnimator = {
    let animator = UIDynamicAnimator(referenceView: self.view)
    animator.debugEnabled = true
    return animator
    }()

If you are writing this code, you’ll notice that you’ll get a compiler error saying that the debugEnabled property is not available on an object of type UIDynamicAnimator. That is absolutely right. This is a debug only method that Apple has provided to us and which we should only use when debugging our apps. Because this property isn’t actually available in the header file of UIDynamicAnimator, we need to create a bridging header (with some small Objective-C code) to enable this property. Create your bridging header and then extend UIDynamicAnimator:

@import UIKit;

#if DEBUG

@interface UIDynamicAnimator (DebuggingOnly)
@property (nonatomic, getter=isDebugEnabled) BOOL debugEnabled;
@end

#endif

When the orange view is repelled by the reversed radial gravity field, it should collide with the edges of your view controller’s view and stay within the bounds of the view:

  lazy var collision: UICollisionBehavior = {
    let collision = UICollisionBehavior(items: [self.orangeView])
    collision.translatesReferenceBoundsIntoBoundary = true
    return collision
    }()

Then create the radial gravity of type UIFieldBehavior. Two properties in this class are quite important:

region
This is of type UIRegion and specifies the region covered by this gravity.
strength
A floating-point value that indicates (id positive) the force by which items get pulled into the gravity field. If you assign a negative value to this property, items get repelled by this gravity field.

In our example, I want the gravity field to consume an area with the radius of 200 points and I want it to repel items:

  lazy var centerGravity: UIFieldBehavior = {
    let centerGravity =
    UIFieldBehavior.radialGravityFieldWithPosition(self.view.center)
    centerGravity.addItem(self.orangeView)
    centerGravity.region = UIRegion(radius: 200)
    centerGravity.strength = -1 //repel items
    return centerGravity
    }()

When the user rotates the device, recenter the gravity:

  override func viewWillTransitionToSize(size: CGSize,
    withTransitionCoordinator
    coordinator: UIViewControllerTransitionCoordinator) {

      super.viewWillTransitionToSize(size,
        withTransitionCoordinator: coordinator)

      centerGravity.position = size.center

  }
Note

Remember the center property that we just added on top of CGSize?

When your view is loaded, add your behaviors to the animator:

  override func viewDidLoad() {
    super.viewDidLoad()

    animator.addBehavior(collision)
    animator.addBehavior(centerGravity)

  }

To handle the panning, consider a few things:

  • When panning begins, you have to disable your animators so that none of the behaviors have an effect on the orange view.
  • When the panning is in progress, you have to move the orange view where the user’s finger is pointing.
  • When the panning ends, you have to re-enable your behaviors.

All this is accomplished in the following code:

  @IBAction func panning(sender: UIPanGestureRecognizer) {

    switch sender.state{
    case .Began:
      collision.removeItem(orangeView)
      centerGravity.removeItem(orangeView)
    case .Changed:
      orangeView.center = sender.locationInView(view)
    case .Ended, .Cancelled:
      collision.addItem(orangeView)
      centerGravity.addItem(orangeView)
    default: ()
    }

  }

See Also

Recipe 13.2Recipe 13.3, and Recipe 13.4

13.2 Creating a Linear Gravity Field on Your UI

Problem

You want to create gravity that follows a vector on your UI.

Solution

Use the linearGravityFieldWithVector(_:) class method of UIFieldBehavior to create your gravity. The parameter to this method is of type CGVector. You can provide your own x- and y-values for this vector when you construct it. This is now your gravity field and you can add it to an animator of type UIDynamicAnimator.

Note

I am basing this recipe on Recipe 13.1. There are some things, such as the bridging header to enable debugging, that I mentioned in Recipe 13.1 and won’t mention again in this recipe. I might skim over them but won’t go into details.

Discussion

Whereas the Recipe 13.1 has a center and a radius, a linear gravity has a direction only (up, down, right, left, etc.). In this example, we are going to have the exact same UI that we created in Recipe 13.1. So create the little orange view on your storyboard and link it to an orangeView outlet on your code. Add a pan gesture recognizer to it as well and add it to a method called panning(_:).

Right now, your view controller’s code should look like this:

import UIKit
import SharedCode

class ViewController: UIViewController {

  @IBOutlet var orangeView: UIView!

  lazy var animator: UIDynamicAnimator = {
    let animator = UIDynamicAnimator(referenceView: self.view)
    animator.debugEnabled = true
    return animator
    }()

  lazy var collision: UICollisionBehavior = {
    let collision = UICollisionBehavior(items: [self.orangeView])
    collision.translatesReferenceBoundsIntoBoundary = true
    return collision
    }()

  ...

The next step is to create your linear gravity:

  lazy var gravity: UIFieldBehavior = {
    let vector = CGVector(dx: 0.4, dy: 1.0)
    let gravity =
    UIFieldBehavior.linearGravityFieldWithVector(vector)
    gravity.addItem(self.orangeView)
    return gravity
    }()

Last but not least, handle the panning and add the effects to the animator (see Recipe 13.1):

  override func viewDidLoad() {
    super.viewDidLoad()

    animator.addBehavior(collision)
    animator.addBehavior(gravity)

  }

  @IBAction func panning(sender: UIPanGestureRecognizer) {

    switch sender.state{
    case .Began:
      collision.removeItem(orangeView)
      gravity.removeItem(orangeView)
    case .Changed:
      orangeView.center = sender.locationInView(view)
    case .Ended, .Cancelled:
      collision.addItem(orangeView)
      gravity.addItem(orangeView)
    default: ()
    }

  }

If you run your app now you should see an interface similar to Figure 13-4. Our linear gravity pulls all objects down and to the right. This is because in our vector earlier I specified a positive y-delta that pulls everything down and a positive x-delta that pulls everything to the right. I suggest that you play around with the delta values of type CGVector to get a feeling for how they affect gravity.

Figure 13-4. Linear gravity acting on an object

You can also go ahead and change some aspects of your gravity field. For instance, set the strength property of the gravity to 20 and see how much more gravity is applied to your objects. Similarly, play with the animationSpeed property of your gravity to set the animation speed.

See Also

Recipe 13.1Recipe 13.3, and Recipe 13.5

13.3 Creating Turbulence Effects with Animations

Problem

You want to simulate turbulence in your animator and have your UI components flail about when they hit the turbulent region.

Solution

Instantiate your turbulence with the turbulenceFieldWithSmoothness(_:animationSpeed:) class method of UIFieldBehavior. Then do the following:

  1. Set the UIFieldBehavior class’s strength property according to your needs.
  2. Set its region property to an instance of UIRegion. This defines in which region of the screen your turbulence behavior is effective.
  3. Set its position property to a CGPoint instance in your reference view.

After you are done setting up the turbulence behavior, add it to your animator of type UIDynamicAnimator.

Discussion

In this recipe, I want to create an effect very similar to what we got in Recipe 13.2, but in addition add a turbulence field in the center of the screen so that, when we take our little orange view (see Figure 13-1) and drop it from the top-left corner of the screen, it will fall down (and to the right; see Figure 13-4). But on its way down, it will hit our turbulence field and its movements will be affected.

Set up your gravity exactly as we did in Recipe 13.2. I won’t go through that here again. Then create a turbulence field in the center of the screen with a radius of 200 points:

  lazy var turbulence: UIFieldBehavior = {
    let turbulence = UIFieldBehavior.turbulenceFieldWithSmoothness(0.5,
      animationSpeed: 60.0)
    turbulence.strength = 12.0
    turbulence.region = UIRegion(radius: 200.0)
    turbulence.position = self.orangeView.bounds.size.center
    turbulence.addItem(self.orangeView)
    return turbulence
  }()

Make sure to add this field to your animator. When the user is panning with the gesture recognizer (see Recipe 13.1), disable all your behaviors, and re-enable them when the panning is finished:

  override func viewDidLoad() {
    super.viewDidLoad()

    animator.addBehavior(collision)
    animator.addBehavior(gravity)
    animator.addBehavior(turbulence)

  }

  @IBAction func panning(sender: UIPanGestureRecognizer) {

    switch sender.state{
    case .Began:
      collision.removeItem(orangeView)
      gravity.removeItem(orangeView)
      turbulence.removeItem(orangeView)
    case .Changed:
      orangeView.center = sender.locationInView(view)
    case .Ended, .Cancelled:
      collision.addItem(orangeView)
      gravity.addItem(orangeView)
      turbulence.addItem(orangeView)
    default: ()
    }

  }

Give it a go and see the results for yourself. Drag the orange view from the top-left corner of the screen and drop it. It will be dragged down and to the right, and when it hits the center of the screen (inside a radius of 200 points), it will wiggle around a bit because of turbulence.

See Also

Recipe 13.1 and Recipe 13.2

13.4 Adding Animated Noise Effects to Your UI

Problem

You want to add a noise field on your UI and have your UI components surf in all directions on this field.

Solution

  1. Create a noise field using the noiseFieldWithSmoothness(_:animationSpeed:) class method of UIFieldBehavior.
  2. Add the views you want affected by this noise to the field using its addItem(_:) method.
  3. Add your noise field to an animator of type UIDynamicAnimator (see Recipe 13.1).
Note

This recipe is based on what you learned in Recipe 13.1, so I won’t be going through all the details that I have already explained.

Discussion

Noise is great for having an item constantly move around on your reference view in random directions. Have a look at the noise field in Figure 13-5. This noise field is shown graphically on our UI using a UI Dynamics debugging trick (see Figure 13-5).

Figure 13-5. Noise field affecting a square view

The direction of the noise that you see on the fields dictates in which direction the field repels the items attached to it. In this case, I’ve used negative gravity (think of it that way). If you want to limit the effective region of your noise field on your reference view, simply set the region property of your field. This is of type UIRegion.

Now create your UI exactly as you did in Recipe 13.1. You should have an orange view that is accessible through the orangeView property of your view controller. Create a collision detector and an animator using what you learned in the aforementioned recipe. Now go ahead and create your noise field:

  lazy var noise: UIFieldBehavior = {
    let noise = UIFieldBehavior.noiseFieldWithSmoothness(0.9,
      animationSpeed: 1)
    noise.addItem(self.orangeView)
    return noise
  }()

Add the noise field to your animator:

  override func viewDidLoad() {
    super.viewDidLoad()
    animator.addBehavior(collision)
    animator.addBehavior(noise)
  }

Last but not least, handle your pan gesture recognizer’s event, so that when the user starts dragging the orange view across the screen, your dynamic behaviors will shut down. And as soon as the user is done with dragging, they will come back up:

  @IBAction func panning(sender: UIPanGestureRecognizer) {

    switch sender.state{
    case .Began:
      collision.removeItem(orangeView)
      noise.removeItem(orangeView)
    case .Changed:
      orangeView.center = sender.locationInView(view)
    case .Ended, .Cancelled:
      collision.addItem(orangeView)
      noise.addItem(orangeView)
    default: ()
    }

  }

See Also

Recipe 13.5

13.5 Creating a Magnetic Effect Between UI Components

Problem

You want to create a magnetic field between two or more UI elements.

Solution

Follow these steps:

  1. Create your animator (see Recipe 13.1).
  2. Create a collision detector of type UICollisionBehavior.
  3. Create a magnetic field of type UIFieldBehavior using the magneticField() class method of UIFieldBehavior.
  4. Add your magnetic field and collision detector to your animator.
Note

I am basing this recipe on what we learned in Recipe 13.4 and Recipe 13.1.

Discussion

Create a UI that looks similar to Figure 13-6.

Figure 13-6. Place three colorful views on your UI

Then link all views to an outlet collection called views in your code:

class ViewController: UIViewController {

  @IBOutlet var views: [UIView]!

  ...

Now that you have an array of views to which you want to apply a noise field and a magnetic field, it’s best to extend UIFieldBehavior so that you can pass it an array of UI elements instead of one element at a time:

extension UIFieldBehavior{
  func addItems(items: [UIDynamicItem]){
    for item in items{
      addItem(item)
    }
  }
}

Also, it’s best to extend UIDynamicAnimator so that you can add all our behaviors to your animator at once:

extension UIDynamicAnimator{
  func addBehaviors(behaviors: [UIDynamicBehavior]){
    for behavior in behaviors{
      addBehavior(behavior)
    }
  }
}

Now add a noise and collision behavior, plus your animator, using what you learned in Recipe 13.4. I won’t repeat that code here. Create a magnetic field and enable it on all your views (see Figure 13-7):

  lazy var magnet: UIFieldBehavior = {
    let magnet = UIFieldBehavior.magneticField()
    magnet.addItems(self.views)
    return magnet
  }()

Last but not least, add your behaviors to the animator:

  var behaviors: [UIDynamicBehavior]{
    return [collision, noise, magnet]
  }

  override func viewDidLoad() {
    super.viewDidLoad()
    animator.addBehaviors(behaviors)
  }

Run the app and see the results for yourself.

Figure 13-7. The magnetic field causes all the views to attract one another

13.6 Designing a Velocity Field on Your UI

Problem

You want to apply force, following a vector, onto your UI components.

Solution

Follow these steps:

  1. Create an animator of type UIDynamicAnimator (see Recipe 13.1).
  2. Create your collision detector of type UICollisionBehavior.
  3. It’s best to also have gravity or other forces applied to your field (see Recipe 13.1 and Recipe 13.2).
  4. Create your velocity of type UIFieldBehavior using this class’s velocityFieldWithVector(_:) method and supplying a vector of type CGVector.
  5. Set the position property of your velocity field to an appropriate point on your reference view.
  6. Set the region property of your velocity to an appropriate region (of type UIRegion) of your reference view.
  7. Once done, add your behaviors to your animator.
Note

I recommend that you have a look at Recipe 13.1 where I described most of the basics of setting up a scene with gravity and an animator. I won’t go into those in detail again.

In this recipe, I am also going to use a few extensions that we coded in Recipe 13.5.

Discussion

A velocity field applies a force toward a given direction to dynamic items, such as UIView instances. In this recipe, I am going to design a field that looks like our field in Recipe 13.5. On top of that, I am going to apply a slight upward and leftbound force that is positioned smack dab in the center of the screen. I am also going to position an orange view on my main storyboard and have all the forces applied to this little poor guy. I will then place the orange view on top of the reference view so that when I run the app, a few things will happen:

  1. The southeast-bound gravity will pull the orange view to the bottom right of the screen.
  2. The orange view will keep falling down until it hits the northwest-bound velocity field, at which point the orange view will get uncomfortable and move up and left a bit a few times, and keep falling until it gets out of the velocity field.
  3. The orange view will then eventually settle at the bottom right of the view.

I now need you to set up your gravity, animator, and collision detector just as you did in Recipe 13.2 so that I don’t have to repeat that code. Then set up the velocity field:

  lazy var velocity: UIFieldBehavior = {
    let vector = CGVector(dx: -0.4, dy: -0.5)
    let velocity = UIFieldBehavior.velocityFieldWithVector(vector)
    velocity.position = self.view.center
    velocity.region = UIRegion(radius: 100.0)
    velocity.addItem(self.orangeView)
    return velocity
  }()

Then batch up all your forces into one variable that you can give to our animator, using the extension we wrote in Recipe 13.5:

  var behaviors: [UIDynamicBehavior]{
    return [self.collision, self.gravity, self.velocity]
  }

  override func viewDidLoad() {
    super.viewDidLoad()
    animator.addBehaviors(behaviors)
  }

And when the user starts panning your orange view around, stop all the forces, then restart them when she is done dragging:

  @IBAction func panning(sender: UIPanGestureRecognizer) {

    switch sender.state{
    case .Began:
      collision.removeItem(orangeView)
      gravity.removeItem(orangeView)
      velocity.removeItem(orangeView)
    case .Changed:
      orangeView.center = sender.locationInView(view)
    case .Ended, .Cancelled:
      collision.addItem(orangeView)
      gravity.addItem(orangeView)
      velocity.addItem(orangeView)
    default: ()
    }

  }

13.7 Handling Nonrectangular Views

Problem

You want to create nonrectangular-shaped views in your app, and want your collision detection to work properly with these views.

Solution

Follow these steps:

  1. Subclass UIView and override the collisionBoundsType variable of type UIDynamicItemCollisionBoundsType. In there, return UIDynamicItemCollisionBoundsType.Path. This makes sure that you have your own Bezier path of type UIBezierPath, and you want that to define the edges of your view, which are essentially the edges that your collision detector has to detect.
  2. Override the collisionBoundingPath variable of type UIBezierPath in your view and in there, return the path that defines your view’s edges.
  3. In your UIBezierPath, create the shape you want for your view. The first point in this shape has to be the center of your shape. You have to draw your shape in a convex and counterclockwise manner.
  4. Override the drawRect(_:) method of your view and draw your path there.
  5. Add your behaviors to your new and awesome view and then create an animator of type UIDynamicAnimator (see Recipe 13.1).
  6. Optionally, throw in a noise field as well to create some random movements between your dynamic items (see Recipe 13.4).
Note

I am going to draw a pentagon view in this recipe. I won’t teach how that is drawn because you can find the basic rules of drawing a pentagon online and that is entirely outside the scope of this book.

Discussion

Here, we are aiming to create a dynamic field that looks like Figure 13-8. The views I have created are a square and a pentagon. We will have proper collision detection between the two views.

Figure 13-8. Square and pentagon with collision detection

Let’s start off by creating a little extension on the StrideThrough structure. You’ll see soon, when we code our pentagon view, that I am going to go through five points of the pentagon that are drawn on the circumference of the bounding circle, plot them on the path, and draw lines between them. I will use stride(from:through:by:) to create the loop. I would like to perform a function over every item in this array of numbers, hence the following extension:

extension StrideThrough{
  func forEach(f: (Generator.Element) -> Void){
    for item in self{
      f(item)
    }
  }
}

Let’s move on to creating a class named PentagonView that subclasses UIView. I want this view to be constructed only by a diameter. This will be the diameter of the bounding circle within which the pentagon will reside. Therefore, we need a diameter variable, along with our constructor and perhaps a nice class method constructor for good measure:

class PentagonView : UIView{

  private var diameter: CGFloat = 0.0

  class func pentagonViewWithDiameter(diameter: CGFloat) -> PentagonView{
    return PentagonView(diameter: diameter)
  }

  init(diameter: CGFloat){
    self.diameter = diameter
    super.init(frame: CGRectMake(0, 0, diameter, diameter))
  }

  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
  }

  var radius: CGFloat{
    return diameter / 2.0
  }

  ...

We need next to create our UIBezierPath. There are five slices inside a pentagon and the angle between each slice, from the center of the pentagon, is 360/5 or 72 degrees. Using this knowledge, we need to be able to, given the center of our pentagon, plot the five points onto the circumference of the bounding circle:

    func pointFromAngle(angle: Double) -> CGPoint{
    
    let x = radius + (radius * cos(CGFloat(angle)))
    let y = radius + (radius * sin(CGFloat(angle)))
    return CGPoint(x: x, y: y)
    
  }
  
  lazy var path: UIBezierPath = {
    let path = UIBezierPath()
    path.moveToPoint(self.pointFromAngle(0))
    
    let oneSlice = (M_PI * 2.0) / 5.0
    let lessOneSlice = (M_PI * 2.0) - oneSlice
    
    oneSlice.stride(through: lessOneSlice, by: oneSlice).forEach{
      path.addLineToPoint(self.pointFromAngle($0))
    }
    
    path.closePath()
    return path
    }()

That was the most important part of this recipe, if you are curious. Once we have the path, we can draw our view using it:

  override func drawRect(rect: CGRect) {
    guard let context = UIGraphicsGetCurrentContext() else{
      return
    }
    UIColor.clearColor().setFill()
    CGContextFillRect(context, rect)
    UIColor.yellowColor().setFill()
    path.fill()
  }

The next and last step in creating our pentagon view is to override the collisionBoundsType and the collisionBoundingPath variable:

  override var collisionBoundsType: UIDynamicItemCollisionBoundsType{
    return UIDynamicItemCollisionBoundsType.Path
  }

  override var collisionBoundingPath: UIBezierPath{
    let path = self.path.copy() as! UIBezierPath
    path.applyTransform(CGAffineTransformMakeTranslation(-radius, -radius))
    return path
  }
Note

I am applying a translation transform on our Bezier path before giving it to the collision detector. The reason behind this is that the first point of our path is in the center of our shape, so we need to subtract the x and y position of the center from the path to translate our path to its actual value for the collision detector to use. Otherwise, the path will be outside the actual pentagon shape. Because the x and y position of the center of our pentagon are in fact the radius of the pentagon and the radius is half the diameter, we provide the radius here to the translation.

Now let’s extend UIView so that we can add a pan gesture recognizer to it with one line of code. Both the square and our pentagon view will easily get a pan gesture recognizer:

extension UIView{
  func createPanGestureRecognizerOn(obj: AnyObject){
    let pgr = UIPanGestureRecognizer(target: obj, action: "panning:")
    addGestureRecognizer(pgr)
  }
}

Let’s move on to the view controller. Add the following components to your view controller, just as we did in Recipe 13.4:

  • An animator of type UIDynamicAnimator
  • A collision detector of type UICollisionBehavior
  • A noise field of type UIFieldBehavior

Let’s bundle the collision detector and the noise field into an array. This lets us add them to our animator faster with the extensions that we created in Recipe 13.5:

  var behaviors: [UIDynamicBehavior]{
    return [self.collision, self.noise]
  }

The next step is to create our square view. This one is easy. It is just a simple view with a pan gesture recognizer:

  lazy var squareView: UIView = {
    let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
    view.createPanGestureRecognizerOn(self)
    view.backgroundColor = UIColor.brownColor()
    return view
    }()

The juicy part, now! The pentagon view. Create it with the constructor of PentagonView and then place it in the center of your view:

  lazy var pentagonView: PentagonView = {
    let view = PentagonView.pentagonViewWithDiameter(100)
    view.createPanGestureRecognizerOn(self)
    view.backgroundColor = UIColor.clearColor()
    view.center = self.view.center
    return view
    }()

Group your views up and add them to your reference view:

  var views: [UIView]{
    return [self.squareView, self.pentagonView]
  }

  override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(squareView)
    view.addSubview(pentagonView)
    animator.addBehaviors(behaviors)
  }

Last but not least, handle panning. As soon as the user starts to pan one of our views around, pause all the behaviors. Once the panning is finished, reenable the behaviors:

  @IBAction func panning(sender: UIPanGestureRecognizer) {

    switch sender.state{
    case .Began:
      collision.removeItems()
      noise.removeItems()
    case .Changed:
      sender.view?.center = sender.locationInView(view)
    case .Ended, .Cancelled:
      collision.addItems(views)
      noise.addItems(views)
    default: ()
    }

  }

Wrapping up, I want to clarify a few things. We extended UIDynamicAnimator and added the addBehaviors(_:) method to it in Recipe 13.5. In the same recipe, we added the addItems(_:) method to UIFieldBehavior. But in our current recipe, we also need removeItems(), so I think it’s best to show that extension again with the new code:

extension UIFieldBehavior{
  public func addItems(items: [UIDynamicItem]){
    for item in items{
      addItem(item)
    }
  }
  public func removeItems(){
    for item in items{
      removeItem(item)
    }
  }
}

Please extend UICollisionBehavior in the exact same way and add the addItems(_:) and removeItems() methods to that class as well.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset