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.
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.
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).
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.
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
!
...
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
UIRegion
and specifies the region covered by this gravity.strength
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
}
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:
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
:
()
}
}
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
.
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.
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.
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.
Instantiate your turbulence with the turbulenceFieldWithSmoothness(_:animationSpeed:)
class method of UIFieldBehavior
. Then do the following:
UIFieldBehavior
class’s strength
property according to your needs.UIRegion
. This defines in which region of the screen your turbulence behavior is effective.CGPoint
instance in your reference view.After you are done setting up the turbulence behavior, add it to your animator of type UIDynamicAnimator
.
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.
noiseFieldWithSmoothness(_:animationSpeed:)
class method of UIFieldBehavior
.addItem(_:)
method.UIDynamicAnimator
(see Recipe 13.1).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.
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).
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
:
()
}
}
UICollisionBehavior
.UIFieldBehavior
using the magneticField()
class method of UIFieldBehavior
.I am basing this recipe on what we learned in Recipe 13.4 and Recipe 13.1.
Create a UI that looks similar to Figure 13-6.
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.
UIDynamicAnimator
(see Recipe 13.1).UICollisionBehavior
.UIFieldBehavior
using this class’s velocityFieldWithVector(_:)
method and supplying a vector of type CGVector
.position
property of your velocity field to an appropriate point on your reference view.region
property of your velocity to an appropriate region (of type UIRegion
) of your reference view.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.
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:
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
:
()
}
}
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.collisionBoundingPath
variable of type UIBezierPath
in your view and in there, return the path that defines your view’s edges.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.drawRect(_:)
method of your view and draw your path there.UIDynamicAnimator
(see Recipe 13.1).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.
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.
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
}
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:
UIDynamicAnimator
UICollisionBehavior
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.