[MUSIC] Standford University.
>> Okay, well, welcome to lecture number 8,
CS193P, Fall of 2017. Today it's all about animation, just
going to continue a little bit of what we talked about Last
time. There's three kinds of information I'm gonna
talk about today. The first is
UIView Property Animation. And it's exactly what
it sounds like. You can animate these yellow
properties on your view. This really the only things
you can animate using this UIView Property Animation
mechanism. But it's pretty powerfu.
You've got the frame and center that animating the
position of the view. You've got the bounds which will
animate the size of the view, although only in a transient
way because it's the frame that determines where you are.
And so If you're going to make it larger,
it occupies more space. In super view, you have to
set the frame. The transform, which is super powerful,
we saw that with the card, where we rotated
the corners upside down. So you can do rotation, you could
also do scaling with that, in fact, that's probably a
better way to do scaling than trying to mock with your
bounds. And then an opacity, another good one, fading views
in and out, appearing and disappearing, and background
color. We almost never do that, but you could do that.
So the way this works is with a class called UIView Property
Animator, not surprisingly. And it works with closures.
And basically the way it works is you set a bunch of things
on your property animator, UIView property animator, to
say what kind of animation you want. How long it's gonna
take, what curve of going through the process quickly or
slowly it's gonna do. If you wanna delay before you start.
All these kinds of things. You set that up and then you
give it a closure. And inside the closure is just code that
modifies these properties. So that's it, then it will
animate those changes that you asked for in the
fashion that you described. Okay so, this is a super
simple way to animate these properties in a view. So let's
take a look at what it looks like to call it now. I'm not
even gonna scratch the surface of UIViewPropertyAnimator's
power, it's amazing. You can create the se animations. You
can scrub them backwards and forwards. You can have
them with auto-repeat and reverse, do all these things. I'm just gonna talk about
the simplest way to use it which is this class method
running property animator. Okay, this thing creates
a property animator that will immediately start running. Now
the arguments to it are very simple. The first one with
duration is just how long this animation is going to take to
happen. So you can make it a one second animation, or a
ten second, solely up to you. Delay is how long to wait
before you start this animation. Why would you wanna
delay the starting animation? Well, maybe you got some other
animation going on you wanna wait, some time for definition
although there's ways to chain animations too, so
that when one finishes, you start another one,
but it might delay for that reason. Then options
we'll talk about later, just various options, how you
want animations to run. And then the all important closure
right there, animations, it takes no arguments, returns
no arguments this closure and you just put the code
that's gonna modify those
properties in ther. And then there's a completion
closure as well. This will get called when the animation
actually finishes running. And that completion one
has an argument there, which the position's
either the start, because you might be running
this animation backwards, believe it or not. Or it's the
end, so the animation got all the way to the end. Or if the
animation got interrupted in the middle, then the position
will be called current, dot current. This is an enum
right here, dot current. Why would an animation get
interrupted in the middle? You start another animation
that animates one of the same properties.
Then that animation wins and it starts to taking over. Now you can have multiple of
these property animators going side by side. All modifying
different properties, but once one starts to pick on
the properties of another one, the later one starts
to win and take over. Okay, and then this
one will stop, go to, say it's complete with the
current position. Now, there's something very important to
understand conceptually about how animations work. And
I'm gonna show you an example of calling this running
property on an, animator. That closure that you passed to it
gets executed immediately. It does not take five
seconds,or ten seconds, or however long for
that closure to execute. It executes immediately and takes effect immediately.
So, the animation is only what the user is seeing.
The user is seeing your animation happen over five or
ten seconds, but actually it happened the instant you
started the animation. Okay, so there's a difference going
on here. There's the reality, which is in your code. That
all happens instantly, then there's the presentation of
it that happens to the user. That's what happens over time.
So, this can be confusing because
you might have an animation that you set to take
go off in two seconds, and it starts running for two
seconds. And, you know you're thinking, yeah, my animation,
when it's done, my frame or my center or my alpha will be
changed. But no, the instant you started that animator,
it got that stuff changed. So you have to think about
these two timeframes. This is what makes animation
somewhat difficult, is thinking about them,
what we call the model, but has nothing to do with
model view controller. But you know the actual
reality in your code, and then the presentation which is
a different thing if we're to user seeing. Alright, so here I'm going to take a view
that's fully opaque and I'm gonna fade it out, and
then when it's gone I'm going to remove it from superview.
So this is basically make a view disappear from the
screen animation. £So first, I'm just checking to make
sure that I'm fully opaque, if my alpha is 1, then I'm
calling the animator here. This animation is gonna take
3 seconds to fade out, and it's not going to start
Until 2 seconds from now. That's what the first
two arguments mean. Then I picked one
option just for fun, which is
allowUserInteraction which means that as it's
fading out, gestures and stuff will still work on it.
Otherwise, if you don't specify this then
as animations happening this kind of animation, you
won't be able to, you know, tap on things or whatever.
Then here's my animations. My animation is just setting
my transparency to zero fully transparent, right?
So, that happens immediately when I call this
method. This method running property animator return
immediately having executed that closure immediately.
And then I have a completion. If the thing gets to the end
without being interrupted, in other words I do fade
all the way out to 0, then I'm going to remove
myself from my super view. That's what this little
completion closure is all about. But notice I put
print alpha equals whatever? That's gonna say alpha
equals 0. Do you see why? Cuz even though it's gonna
take five seconds for that alpha to go to zero it
goes to zero immediately, because I executed
this animation. So I've said that I'm sure your
like yeah makes sense, but until you start coding it up
you're gonna be like woah, that's right,
I already changed that, it just hasn't
appeared on screen, so you have to get a little bit
used to that. What are some of the options that you can
do when you're doing these animations? The first one,
begin from current state, is if you're animating some
property and then you start another animation that
animates the same property. Does it start from the real
value of the property, which is what it got set to, like
alpha equals zero? So does it jump to transparent and
start animating from there? Or does it pick up from wherever
that other animation was? This is really, do you use the
state of alpha that is being animated or do you use the
real state of alpha which is what's in the code which
using our previous slide, would be zero? Okay, so this
is kinda like picking up or just using the real version.
We do this quite a bit if we have overlapping
animations that are doing the same property by two
different animations. What else we got in there?
We got repeat and autoreverse. So you can have animations
that can go forwards and backwards and
forwards and backwards. It's kind of in a loop.
That's kind of fun. Down at the bottom you see
these curves. Curve ease in, ease out. For example,
third from the bottom. That's just saying that
when you move the thing, does it move linearly?
That's curve linear, which would be like it would
move like that, or does it kind of ease into moving
slowly then pick up speed and then slow down at the end? Now
why would you want it to do that? Well things that move
like very linearly feel kind of robotic and mechanical.
Things that ease in and then ease out, feel a little
more like someone picked them up and moved them over and
put them down. You see. So it is just kind of more
natural movement. So like for moving things you almost
always want curve ease in, ease out. But other ones like
maybe fading out to alpha. Maybe you don't need that you
can just linearly fade out. So that's it for view animation,
super, super easy. You can only animate those
properties. It's super easy to do. And again,
I only scratched the surface. I showed you one method in
UIViewPropertyAnimator. It has dozens of methods, lots of capability in there.
All right, so let's talk about a totally
different kinda animation now. This is animating
an entire view change. So you've got a view and
it's gonna completely change the way it looks and you wanna
animate that in some ways. And there's limited
ways to do that. The classic example of this is
a playing card. When a playing card is face down, it looks
like the Stanford logo or it's the back of a card. When
it's face up it's got pips and corners and all of that stuff. Okay, well when I wanna flip
my card over I could just change it immediately and
it would just change back and forth starkly, but this allows
me to actually flip it over. Like a 3D looking flip. And you can also cross dissolve
from one thing to another. Those are the two major things
you can do here. There's curl up from the bottom too, which
makes it like it's curling up, and you're looking at a piece
of paper behind it and that's the new version. But that's only for views that
fill the whole screen. Okay, that's not for,
it doesn't really work if it's a view inside another view. It
kind of doesn't feel right. So this is for transitioning
just smoothing out or animating the complete
change to a view. Play cards being the classic example.
Here's how you would call it. You would use this method in
UIView. It's a class method called transition with View.
And you give again, duration. You give the option.
Same options as we had before. Also with some options like
transition flip from left, which means flip this
thing from the left edge. You can also have
flip from top, flip from bottom,
cross dissolve, those kind of things. And
now, in the animation block, you're not limited to just
changing view properties, you can change
anything you want, that's going to make that
thing look different. And then, so what the animation
system does is, it draws your view before this closure.
Then it executes closure, draws the view after, and
then it just flips it over or cross dissolves it,
see what's going on here? So this is kind of a simple
animator for big changes. Playing card is a great
example and we'll see that in the demo that I'm going to do.
All right, so that's number two. Here's number three. This
one's a little more powerful, a little more complicated. Dynamic animation, it's a
little approach to animation. Here we're gonna set up
physics attributes on our views and then just tell them
to go do what they do. So we're talking physics like
density, friction, gravity. Stuff like that.
You put on them there and then they just start moving
because physics, it works. So let's take a look at how you
make this happen. The first, there's three steps
to making it happen. First, you need an animator. This is just the thing
that drives the animation. It's an instance of a class
called UIDynamicAnimator, it only takes one argument
in it's initializer, which is the view that is
going to be the reference coordinate system for
all animation that's going on. And the only requirement for
this view, can be any view in your app. It just has to be a
super view, or a super view of a super view, or
a super view of a super view. It has to be at the top of
the view hierarchy of all the views that the animator
is going to animate. But those views don't all
have to be in the same view. They could be in
sub-views of other views. As long as they all
are eventually have this view. The reference view as a super
view. So a lot of times people wanna make their whole view
controller's view, and that's what I'm
gonna do in my demo. Be a reference view cuz I
don't have any other views. But like in your assignment
you might just make your view that contains your cards
be your reference view. Because you're only gonna
animate the cards. So you don't need to go higher
level, all right? And in fact, it's better to stay lower
down, because it can be more object oriented to have that
animation code down where it's actually happening instead of
putting everything up in your controller. But I will say by
the way, especially when you see me do demos,
which are really lightweight, there is a tendency you
probably have as beginning iOS people to put everything in
your view controller when maybe it wants to be down
a level in a custom view. So just food for
thought there. Think about what level things
wanna be at. All right, so number one is to
create the animator, number two is to
create behaviors. So this is describing how the
things in this view behave. And so we're talking
about gravity, and whether things collide with
each other, stuff like that. Okay, those are behaviors,
and you add the behaviors to the animator, so animator
has an add behavior method. You call it after
creating a behavior and you just add it. Now as soon
as you add that behavior. That animator would start
enforcing that behavior, whatever it is gravity or
collisions or whatever. But
there is no items yet that are being affected
by the behaviors. So step three is to add
items to the behaviors. So it's creating an animator,
add behaviors to the animator. Now add items to the behavior.
The instant you add an item to a behavior, it will
start being affected by that behavior assuming
it's in an animator. And UIView's are the items
here, but actually there's not UIView's it's any object
that implements the protocol UIDynamicItem, which
doesn't have to be a view. I'm gonna show you that
protocol in a second. But UIViews are 99% of the time
what we pass items to these behaviors. So if I say
gravity add item, item 1, item 1 will immediately start
feeling the pull of gravity. If I say collider at item 1
immediately item 1 will start colliding with everything else
that is added as an item in collider, in the collider. You
don't say go. It's just like as soon as you add an item it
starts being effected by that behavior. And if you remove an
item it instantly stops being effected by that behavior. here's that UI dynamic item
protocol that view implements. Okay, so View actually implemented
three of them automatically. It already had a bound,
center, and transform. Okay so it automatically implemented
those. And then the other two are just really simple things
having to do with collisions. I'm not even gonna
talk about those. But you can see by what's in
the dynamic item protocol what the animation system
is Is able to animate. It's able to animate
the size through the bounds. It's able to animate the
position through the center. And it's able to animate
the transform, so it can rotate and scale and do anything it wants
pretty much in that front. And it tends to mostly use
transform in center to do its animation. Okay, the bound
to be notice is read only. So the bounds is just for the
views that can obviously look at their own bound from
their drawing and stuff. The animator doesn't actually
change the bounds, as it goes. Mostly doing center and
transform. By the way, if you give an object to the
animator by making a behavior operate on it, really
animator kind of owns it. It owns a center and transform as it's going
to change them around. So if you wanted to change
a standard, or transform off a view you already gave to
a behavior, then you have to call this method in your
dynamic animator which is update the item to
the current state. In other words,
I changed the center or the transform, please, Mr.
Animator, take that. And so the animator would take
that state, move the object, change its transform, and then
start behaving on it again. Keep going forward, behaving
from there. All right, so let's talk about some of the behaviors you can have.
I mentioned gravity. Gravity is an easy one.
By default, gravity is down, meaning down towards
the home button or the bottom of the device. Like
an iPhone 10, it's away from the face recognition stuff at
the top. And the magnitude, we sometimes call it G,
but it's not really G. The magnitude of 1.0 is 1,000
points per second per second. Everyone knows what
gravity is, right? It's an acceleration,
9.8 meters per second squared. So, this is 1,000 points
per second per second. Now, what's incredible is
1,000 points per second per second feels a lot like 9.8
meters per second squared. In other words, if I put an
object at the top of my screen and added to it a behavior
with that gravity, it falls at about the same rate as
a real life object would fall. It's amazing that, that round number ends up
being like that. But it is. And then also, gravity doesn't
have to be down. You can make gravity go up. Or off to the
right or anywhere you want. So you can completely control
what is going on with gravity. Attachment behavior,
really cool one. Think of it as
like an iron bar, or a bar between either two
items or between an item and a fixed point, all right? And
that bar keeps those two items connected even as all the
other behaviors work on it. So, imagine you had two things
connected with a detachment behavior. So they're behaving
as if they're attached to each other. And they start to fall
because they're both being operated on by gravity. Then
let's say one of them collides with something.
That bark is gonna make it, so the other one which
didn't collide is gonna swing like a pendulum, you
see? Cuz this one came down, it collided, and
this one kept on going. But the bar keeps them together.
And same thing if you attached it to a fixed point. And then
you turn on gravity, it will start to fall down. But then
when it gets to the bottom, it'll start swinging like a
pendulum. And then eventually, gravity will pull it so that it's straight down. You
see what I'm saying there? So attachments, awesome, cool
little things. Another thing that's cool about them is you
can change the length of this bar while the animation's
happening. So it's animating. Things are falling, bouncing
off, things are colliding with things. They're attached,
and you can make the bar get closer together or farther
apart. You can also make the bar kind of springy with a
certain amount of damping. So that things hit something, and
then it will come together, and then come back up to make
their attachment be the right distance.
It's pretty cool behavior. Then there's collision
behavior. This is probably the most common behavior.
Okay, this is objects UIView bouncing into each other or
bouncing off bezier paths, basically, in the background.
And you set this up just by adding any fix boundaries
you want as bezier paths, usually, and then adding
items. And you can control whether the items bounce off
each other or only off these boundaries as bezier paths as
you set up. All these bezier paths are in the reference
views coordinate system, by the way. They're not
actually drawn or anything. They're just conceptually
boundaries in the space. And there's a really
cool var in there. The second from the bottom
there translates reference bounds into boundary.
If you set that to true on a collision behavior,
then your reference views outer edges will become a
boundary, which is common cuz you got things bouncing around
in your reference view. They'll stay mostly inside.
Now, one thing about collision boundaries, a lot of people
think, if I put my reference bounds as collision boundary,
then no object will ever get out. Okay, it will never
escape. But that's not true. Because items can, for
example, move so fast that in one animation frame, they move
from being on one side of the boundary to the other.
And then they just fly off in the outer space,
they're gone forever. So collision boundaries,
they only check for collisions on each frame
of the animation, and so they're not a lock down
guarantee. You keep things inside if you put a collision
boundary around everything. Now, collision boundaries
also have a delegate. Their delegate allows you to find
out when collisions happen. So if you set something to
be a collision delegate, you'll get these methods like
collision behavior, began, contact for some dynamic
item with a boundary and an identifier with a name. By
the way, notice the boundary identifiers are of
type NSCopying. That's really weird
old Objective-C thing. That just means that they're
either an NS number or an NS string. And so you can use S
to turn them into a string or a double or an int, because
we know that NS number and NS string can be like
automatically as to their Swift counterparts there.
Collisions are cool. SnapBehavior, also
very common. This is when you're using the dynamic
animation system, how you move something. So you wanna
move something someplace else. You're not doing view property
animation here, you're using dynamic animation, so
you say snap to this point. To little better than
the view property 1, because when it snaps there,
it doesn't just jump right there or even ease
out and ease in there. It actually gets there,
and when it's there, imagine it has four springs
on the corner. So it kind of comes in and vibrates a
little. So it feels even more natural flying across the
screen and stopping. So you'll probably use the snap behavior
in your homework because you have to throw matched cards
into a discard pile. You're probably gonna use snapTo
to throw them out there, cuz you're gonna
be doing dynamic animation with them otherwise.
Then there's PushBehavior. Okay, PushBehavior
just pushes an object. And it can either constantly
push it. Or it can just push it once, like punch it. And
this is an interesting one. You can specify the angle and
the magnitude of the push. The instantaneous one is kind
of interesting, because think about it. This is a behavior
that's added to an animator, and this behavior only fires
once if it's instantaneous. And then it's just sitting
there doing nothing forever. So it's just kinda croft.
So it'd be nice if there was a way that we could
add a PushBehavior and say, after you fired, please delete yourself because
you've already done your work. And I'm gonna show you how
to do that, but it's gonna require me to teach you
a little more about closures, which I'll do in a little bit.
Another one is a UIDynamicItemBehavior. This
is like a meta behavior. So this is a behavior where you
specify things like friction and elasticity and whether you
allow rotation of the view as it's bouncing off of things. And this affects how all the
other behaviors work, right? If you add more friction, then
obviously, gravity pulls on things. They move slower,
because they have friction, etc. You can also ask the
UIDynamicItemBehavior about all of its items. Things like,
what's your current velocity. How fast are you moving
across the screen, or even how fast are you spinning
if you happen to be spinning, how fast is that happening? So UIDynamicItemBehavior,
we almost always have all or items in
a UIDynamicItemBehavior, because we wanna be able to
set these various things about them. Then there's
UIDynamicBehavior, which is the super class
of all of these behaviors, collision behavior,
gravity behavior, all of these things. Now,
you could subclass this and try and
write your own behavior, but writing a gravity behavior
is pretty hard. A lot of math involved there, trying to get
that to work. But that's not why you use a subclass of
UIDynamicBehavior. What you do with UIDynamicBehavior is
you collect other behaviors, like collisions and gravity
and all this other stuff, into one behavior. So that you
have one behavior that you add your items to, and it's got
all these children behaviors that are making it
behave in all this way. We're gonna do some of
the demo as well, and the way you do this is
you call addChildBehavior on yourself if you're
UIDynamicBehavior sublass. And it now causes that behavior
to be your behavior. So very simple.
UIDynamicBehavior also has a bar in it called
dynamic animator. That is the animator you are
currently being animated by, if any. Okay, so you can
look at this to find out. Am I currently being animated?
This behavior or is it not being animated?
And if it is being animated, who by? And it will even
send you a message. Will move to animator when
you switch to a different animator. Usually when you
go from not being animated to being animated or
vice versa, all right? Now UIDynamicBehavior
has another awesome var that you inherit when
you create a subclass or when the subclasses
are created. Which is action. So action is
a closure. Takes no arguments, returns no arguments. This
closure gets executed every time this UIDynamicBehavior
Acts. So like a push behavior that's instantaneous, this
gets called once. Cuz only once does it act on object.
But a collision behavior is kind of always acting on the
outer gravity behavior always. So it's this thing is
getting called a lot. So never put any code in there
that takes a long time to execute because it will slow
your animation right way down. Cuz these things are being
called all the time, these action closures
right here. But they can be kind of useful.
For example, you might wanna check in here how's my view
left the building, right? You could look at whatever
the items of this behavior's acting on and did it cause
this thing to go outside the reference bounds even?
Or whatever, so that I have to put it
back or maybe destroy it, or something like that. So
again, it only takes a couple lines of code to check the
bounds of your item against the reference bounds and
see if anything has happened. So that would be okay
to put in your action. Now we're gonna use this
action thing in a second to fix that push behavior problem
to, all right? Finally, I'm gonna talk about stasis
of the animator. Really, most of the time we design
these animation mechanisms, with all of the gravity and
collision boundaries and physics and all that stuff,
we design it in a way that we expect it to come to a stop.
Okay, and then maybe a push happens, and
it goes back active again. And then it eventually comes
to a stop. Most of the time, it's not required. You could
have things just constantly going around all the time.
But usually you push, whatever comes to a stop. And
so you can find out when it reaches stasis with the
UIDynamicAnimator's delegate which has these two methods.
Did pause and will resume. And it will tell you,
I came to stasis. I'm not currently
moving anything and then something happens. It'll
say I'm resuming because I have to move things. Okay,
all right? Let's go back and talk about that push behavior
thing. So what I really wanted to do something like thing. I
got this push behavior, right? I set it's magnitude and its
angle. But I really want that push behavior to get thrown
away as soon as it pushes because it's instantaneous.
You see here, my thing here is instantaneous. Push, so
it's only going to push once. And then I want it to
get thrown away, so it doesn't muck up the heap
with a totally useless behavior, all right? Well, I can do that with
this action method, the yellow part right here.
So I can just have the push behaviors action all behaviors
inherit this action closure. And inside the closure I can
tell, ask the push behaviors animator, please remove me.
Okay, remove behavior. The push behavior itself.
So that's cool, right? It solves the problem. Well
yeah, but red on the slides. This creates a memory cycle.
So we're gonna talk a little bit about this memory
cycle that this creates. And the bottom line here is that
the push behavior is pointing to that closure. That closure is pointing
back to the push behavior. So both of them have a pointer
to something them in the heap. And so they're both being kept
in the heap. All right, so for me to tell you how we're
gonna fix that memory cycle, I'm gonna have to take
an aside here and go back and teach you a little more
about closures. Hopefully, you've read about this in
your homework assignment. But here we go. You can define.
When you have a closure you can define some
local variables. Fun little local variables. You put a little square
brackets with these local brackets in there right before
you're in with your closure and you can define any
variables you want. And you can set them to
have some initial value. So here I have variables x and y.
And I can use x and y inside my closure. And then I'll have
whatever value I set there. It doesn't seem like
it's of very much use. And you're right it's
not because, of course, I could use some
instance of a class, or I could just type hello
inside my closure. Why do I need to have the
silly little variables there? Well the reason they're not so
silly is because they can be declared weak. These x and
y are different variables. They're local variables
just for the closure. And if you were to declare one of
them weak, then it now becomes an optional, first of all.
Because we know all weak variables, they don't keep
things in the heap. And they also have to be weak because
if the thing they're pointing to goes out of the heap
they get set to nil, right? So I can now use x and
y still inside there but x is now optional and weak.
Okay, and you're gonna see why that's valuable in a second.
I can even declare that those variables be unowned. And if
you remember back to the thing when I was talking about
memory management, unowned means it's not
reference counted. It's like Swift says okay, it's on you. If you are gonna
access that thing, it better be in the heap because if it's
not, you're crashing. That's basically what unowned means.
So that means I can use x and y inside the closure.
It's not an optional, don't have to worry about that.
But if I use x in there and it's not in the heap, it's
gonna crash my app. Okay, so why is it valuable to do weak
and unowned? Well because weak and unowned variables, whether
they're these little locals or not, don't keep other
things in the heap. They're not strong. They're
weak or even unowned. So we can use that to break
these memory cycles. So here's another example of
a memory cycle that happens all the time. Have a class
here, Zerg, it's called. It has a var called foo which
whose value is a closure. Inside that closure,
I've set it to have a value of a closure that calls
another function in Zerg, which is bar. This is a memory
cycle. And this is a memory cycle because foo, the
closure, is repressing self. Self is keeping the class
Zerg, the instance of the class Zerg, in memory.
And of course, Zerg is keeping the closure in memory cuz it
has a var that points to it. So they're pointing
to each other and keeping each other in memory.
So that's bad. You got one pointing
to the other, the other points to the other,
so how are we going to
break this cycle? Well, we're gonna use those
little local variable hoo-has. So I'm gonna create a little
local variable. It's weak. I'm gonna call it weakSelf.
I'm gonna say equal to Self. And then inside, I'm gonna
say weakSelf, question mark, meaning optional chaining,
.bar. So now there's nothing inside
that closure, no variable in that closure is keeping
a strong pointer to self. And so it's not keeping itself
in the heap. Do you agree? Right, the only thing that's
even used inside that closure is weakSelf, but we know that
weak. Can't keep something in the heap. Now, unfortunately,
it's an optional, so I have to optional chain,
but I've broken the cycle. So this is how we break these
cycles using weak little local variables in our closure.
By the way, we are allowed to call that weak self, self.
In other words, it can have exactly the same name as
a variable in the outer scope. So on this slide,
there's two different selfs. There's the yellow self.
That's a local in the closure. And there's the green self. That's the more global self of
the instance of this class. Okay, so it's two
different variables. But I'm not using the green self
inside the closure at all. I'm only using the yellow
self. And that one's weak. And in fact, this is so common you
don't even have to say equal self. If you say weak self,
it will automatically set it equal to a variable of the
same name in the surrounding code. Okay, so this is how
we break these cycles. So now let's go back to
UI push behavior and look how we would use unowned.
All right, so here's my push behavior code.
If I take that same thing, that action, and just say
unowned push behavior in that, I've broken this cycle.
Because now pushBehavior is no longer managed by the
reference accounting system and, but pushBehavior better
be in the heap right here. Okay, if it does it in the
heap, we're gonna crash. But of course, pushBehavior is
gonna be in the heap there. pushBehavior couldn't possibly
even be executing here. It's action could not be executed
if it wasn't in the heap. So we're guaranteed that that
pushBehavior's gonna be there. So this is a classic one where
we would do unowned to break the cycle. We could've
also done week there and maybe checked to see if it was
nil. But it's all unnecessary cuz we know that's never gonna
be nil there. It's never gonna not be in the heap.So now
I'm going to do a demo. We're gonna take the playing
card thing we did last week. And I'm gonna make
lots of playing cards. And we're gonna put
them in motion, and have them flipping over,
and all kinds of fun stuff. Friday, I'm not gonna
get back to the slide. So let me tell
you that Friday, we will have a Friday section.
It's on source code control. So don't miss that one,
that's a good one. That's the thing we're always
not clicking when we create new projects that say, do you
wanna manage this? Connecting your code to GitHub and all
that stuff is part of that. Next week, actually,
I say these, but actually, we'll definitely do view
controller lifecycle. But I'd probably gonna do
multi-threading next week, which is an important topic. And then maybe some
miscellaneous other UI things, like scroll view, text field,
things like that. Probably do table view and
collection view, and drag and drop, even, the week
after that. All right, so I told you I would show you
the homework animation thing, so let me do that. Let's
go do it here, where is it? Okay, so again, I'm only gonna
show you this briefly. But I just wanted to give you an
idea of what you're gonna do. Your set app, when it comes
up, is going to automatically deal out the cards. You see
how I'm dealing out the cards? They're flying out, and they're also flipping
when they get there. When I deal new cards, you
see, it's smoothly animated to smaller cards, you see? And
then dealt out new cards, so you're gonna have to do that.
Notice, I only have diamonds. That's cuz I wanna get closer
to the end game faster. This is debugging thing. Also, any three cards are a
set right here. So we do this. Now see what happens
when you get a set? They go chaos, they just
fly all over the place. But then they eventually collect
over here in my discard pile. See, look, watch this again. See them flying all over
the place? And notice, it's also dealing out new
cards to replace them. Eventually, I'll get
towards the end here. There won't be any new cards. Notice that my deck nicely
disappeared. That's not a required task, but you
probably wanna do that. And then, of course, as my number
gets smaller, you're still doing the chaos animation. And
we're rearranging the cards as they go down.
Understand what we're doing? So you're basically doing
three animations there. One is you're dealing the
cards out and flipping them. Another one is, when cards
match, you're exploding them. And then putting them in
the discard pile. And then every time the number of
cards changes, you're smoothly animating to the new number.
You're not jumping them. That's it, all right,
that's it. So the demo for today, which will hopefully
help you, let's go here. All right, so the demo
that we're gonna do today, which will help you with your
homework, is to take our card app and put these cards in
motion. Now this is pretty much the same exact card
app we had last time. Except that the difference is
we used to have one card in the middle. In this case,
I've created 12 cards. And I've used my viewDidLoad to
make them be random cards. I'm just randomly going through,
creating these cards, face up, putting the rank and suit on
them to create random cards. So let's see what
this looks like. All right, there's my random
cards. They don't do anything. I don't have any
gestures on them. So I can't tap on them or do
anything. So the first thing I wanna do is add a tap
gesture that flips them over. And we're gonna
start this app and flip them over
without animation. Then we'll add animation to
have them flip, all right? So let's do that. I also heard
there was some confusion in office hours about gestures. So this'll be a good
opportunity to review gestures again. So what I'm gonna do
here, first of all, I'm gonna start my cards face down
instead of face up. And then I'm just gonna add a gesture
recognizer to them all. add, let's make
this code big so you can really see what's
going on here, right? So I'm gonna
addGestureRecognizer, oops, gotta add it to a view.
So I'm gonna add my cardView.addGestureRecognizer,
hello. How come, there we go. So I'm adding a gesture
recognizer. The gesture recognizer I'm gonna add is
UITapGestureRecognizer. And then just to review what
the arguments are to a gesture recognizer, there's
the target. This is the object that's
going to be sent this action method when the gesture
happens. So I'm gonna have the target of this be myself,
the view controller. But it could be the view, I could
send it to that view as well. And this selector
is just #selector. And then inside here, you
put the name of the method. But only the external
names of the arguments. So, for example, I'm gonna
do flipCard. And it's only gonna have one argument,
which is the tap gesture. And I'm gonna have it
have no external name, so I'm gonna do_:. So this is
the name of the method with all the external names and
colons in there, but nothing else. That's all,
everybody cool with that? Now, This is just complaining cuz
I haven't defined flipCard. So now when I create flipCard,
of course, it has to be objc. Okay, all these things that
do this action thing have to be objc. Call it flipCard,
has no external name. It's gonna be the recognizer
UITapGestureRecognizer. Recognizer, sometimes it
makes me type it all in. See if that gets rid
of our warning here. Okay, what's it saying here?
What do we, the objc func, how about that?
It's rebuilding here. Okay, good, that's fixed,
are we over here? Did I forget something here?
Okay, good, code's a little slow to keep
up with my typing today. Okay, so now I have this recognizer.
Now I want to just find out which card was clicked on,
and then flip it over. And this is something that
I think people are like, how do I do that? And
the answer here, of course, I'm gonna switch on the
recognizer.state. We almost always switch on the state.
That's the first thing we do. It's a tap gesture, so I only
care about the ended state. All the other states,
I'm just gonna break, right? But if I get a tap, what I
wanna do is get the card that was tapped on.
But I'm in my view controller. Now normally, I might have the
target have been the view so it would know that it was
itself that got tapped on. But here,
I'm in the view controller, so I have to find it.
Well, it turns out, TapGestureRecognizer, it knows
what view it was tapped on. Remember how I said each
of the concrete gestures have information about
what happened? Well, one of the things is they know
what they were tapped on or whatever. So I can say,
for example, if let the chosenCardView
= the recognizer's view. So view is a var in
TapGestureRecognizer, which is the view that was
tapped on. This is maybe the piece some of you
didn't get from the doc. As a PlayingCardView. Now why do
I need it as PlayingCardView? Because this is just
a UI view, of course. TapGestureRecognizer doesn't
know anything about PlayingCardView. So I have
to make sure it is, in fact, a PlayingCardView. It should
be because that's the only thing that I added any of
these tap gestures to. Then I can take the chosenCardView
here, and have it isFaceUp. =not the chosenCard view
isFaceUp. So I'm just flipping the card over here, right?
Everybody understand that? Okay, so let's go ahead and
run and see if this works. Okay, so they're all
face down, that's good. See I changed them all here
to be false. So let's click, all right. Flipped it over,
working nicely, okay. But of course we want
this to be animated. And we know that that is really
easy to do with that UI view transition with a view thing.
So how do we do that, how do we get started on that?
Let's just go right here and surround this and put this
in a closure that's in a UIview transition,
so UIView.trasition. By the way be careful, their is two different UIView
transitions. There is with right here, which is the one
we want and there is from to. The from to is when you're
transitioning from one view to a completely different view.
And you're going to remove one from the superview and add the
other one in as a subview. So it's a similar thing, but it's like card where the back
of the card goes one view and the front of the card is
a different view. But here we're transitioning
with the same view, cuz we can turn it face up and face down. Here I'll go
ahead and spread this out so you can see these arguments
a little more clearly, right. Okay, the arguments,
it seems like there's a lot of arguments here but
they're all quite simple. So the view we're flipping is
the chosenCardView of course, all right? The duration,
I find like a half a second, maybe six-tenths of a second
is a good amount of time to give it to flip over. Any
slower and it feels kind of glacial. Any faster and it's
like, what happened there? It kind of flashes at you, so you know. Options, here's
where we have to say that we want to flip as opposed to
cross dissolve or anything. So I'm gonna make transition,
flip from, let's flip from the left. So we gonna flip
from the left. Here's the animation's closure, okay
this is where we're going to do whatever cause is the view
to look totally difference. So, in our case it's
this code right here, put this inside here. Don't
need that way over there. But and then completion, for now we're not gonna do
anything when it completes. So I'm just gonna take the
completion thing completely out of there. And
we'll put this down here, whoops, Ctrl+I just to make
things a bit neater there. Okay, everyone got that?
So here's our transition. With this view, flip from
left, we're just going to flip the card over. Let's give it
a try. Okay, here we go. Woo, see, and look, see, how it
kind of shades a little bit. The shading, the kind of gray
that gives it that 3D effect. It feels a little more like a
3D flip. And it flips it over. Okay, so that was easy. So
that's transition animation, totally trivial. Let's we're
not really going to make our app here into
a concentration app. We're kinda doing a little
bit of a UI study here, but what if we wanted this
to be like concentration where we're trying to find
two matching cards? Okay, but we're going to make ours
a little bit harder. We're going to make it so that
when you flip over a card, so here is this card flipped
out. If I flip another card, if they don't match it's
gonna go like this, flip and then immediately
flip them back down. Hm, so you really are going
to have to concentrate. Okay, this really is concentration
cause they go clip, and then flip,
didn't match. You see? So we're gonna have to really
watch what we're doing here. So let's add that animation,
that just turns cards down. Any time there's two cards up,
we're gonna turn them down. So to do that, I need a little
private var that tells me what my faceUpCardViews are.
Which is gonna be an array of PlayingCardViews here. And I'm
going to have it be computed. And we know how to use filters
and all that stuff, so I'm going to have my face
up cards be my cardViews, return my cardVews
filtered and the filter is that they
are face up, right? Let's also make sure that they're not
hidden. Because I'm gonna be hiding cards that are already
matched a little bit later. So I just don't forget
we'll make that be hidden. So now we're able to find out
what our face up cards is. So now what I'm going to do is
after this flip over finishes, I'm going to add
a completion handler. Okay, completion,
which I didn't have before, I'm adding it. This one has
finished. It doesn't have the position, end, start,
current. It just has finished, yes or no, whether this flip
finished. Because, it doesn't really make sense to move your
flip backwards and forwards. It kind of either happens or
it doesn't. So inside here, I'm gonna do another
transition. Okay so actually I'll just copy and
paste why not. Copy, paste and it's perfectly fine to have
animations in the completion handlers of other animations.
That's perfectly fine. So what am I gonna do here? Here I'm gonna flip down
all the face up cards if there's two.
So let's go ahead and check, if my faceUpCardViews.count
== 2, so I've got 2 face up cards. Then
I'm gonna flip them all face down using another transition.
So let me show you a cool way to do for loops if you
have an array. Watch this, and see here, what it's saying?
Let's stop and look at this. I got an error here, it says
Reference property in closure requires explicit self to make
capture semantics explicit. Okay, this is awesome. Swift if saying,
wait a second here bud, you are accessing
a bar on yourself, and that's gonna
capture yourself. And I want you to type,
self., right there, so that you realize that you
might have a loop here, a memory cycle. So isn't that
cool Swift made us do that. I really appreciate
that Swift does that. Cuz otherwise it could be
really easy to forget. Oops, this is self and then
realize, I got a memory cycle. But do we actually have
a memory cycle here? No, we don't, because while this
closure does capture self. Self does not in any way
point to this closure. It's not part of any var. It's
not any part of a dictionary or an array or
anything that self has. It's a closure we're giving
off to the animation system. So only the animation system
has a pointer to it. So there's no cycle here. So
there's no reason for us to do any of those weird local
variables that are weak or any of that. All right so,
if I have two face cards, I'm gonna do a for loop of
all the face cards, but I'm going to do it like this. Watch this, Face up cards,
forEach, do a closure. So, I'm going to execute a closure for
each of these face up cards. So, that is kinda a cool
way to do for loop, and inside there I'm gonna put
this here. And of course in the for each $0 is each of the
things in this so I'm gonna do this flip transition with
each of the cards in here. Same thing same thing.
Here's it's $0 is face up and it's not flipped over, it's
false. We want them face down. You got all that? Make sense?
What do we got here? Let's get rid of this
right here also. And what's happening here?
$0 is face up. Okay, anyone see what I'm
doing wrong there? Look at my card here, cardView, maybe I
need to do this, cardView in. Oops, cardView or something like that. That fix it, yeah
that did, that's interesting. So the $0, I couldn't get
it to type right there. I don't know why it couldn't
infer that Here, if we have two cards face up we
will flip them both face down. Let's see if that works. All
right so, one card face up. We can actually turn this card
back face down, by the way. Face up, here we go,
second card. All right, we're good. Excellent.
Now what happens if two of these cards match like that?
These two cards, they match, so it shouldn't do that,
it shouldn't turn it down. It needs to give us some
animation that says woohoo you matched two cards. Okay, so
let's come up with that, an animation to do that.
So the animation I'm gonna do when cards match is I'm gonna
make the cards really big to emphasize that
you have a match, then I'm gonna make
them really small and have them fade away to
nothingness and disappear. Because when cards match, of
course I wanna take them away. So that's what we're gonna do. Now that's a two
step animation. The first step is to
make them big and then the second step is
to animation down. And we can do that with the UIView
Property Animator, of course, because of the size of
the view and it's transparency are animateable properties.
And to make it two step like that, we'll use
the completion thing, similar to how we did this,
right. We waited till the first one
finished and then we went to do the other one. All right,
so how are we gonna do that? Well, I need another private
var here, actually. That's just gonna tell me whether
the face up cards match. So I'm gonna call that
faceUpCardViewsMatch, I think I called that.
Is that what I called it? Yes, faceUpCardViewsMatch.
It's a bool, we're going to calculate that as well,
I'm just going to return, if our
faceupcardviews.count=2, and oops. Kind of made
it on the fly. And face up, I'm missing
an A there, faceUpCardViews [0].rank=FaceUp, and you can kind of see we
don't have a model here so we're kind of looking at
these things directly. Again, this is not how you would do
this if you were going forward to turn this into a
concentration game. You would have a model and all that. I'm
really just doing like I said, kind of a UI case
study just to see what would it look like and
then later I would go back and do my actual model or
whatever. So this is just a bool to tell us whether
two face up cards match. So down here after I've
done my first card flip, I'm gonna say if my self,
of course, face up cards views match. Then, here
I'm going to do the animation. Otherwise I'll going to go on
and check just to see if we have two face cards that don't
match. Then we'll do that flip down thing. But here we're
not going to flip them down. We're going to make them big
and then make them small. So we're going to use our
view eye view property animator here, very simple
to do. We're just gonna say UIViewPropertyAnimator.runnin-
gPropertyAnimator. Again I will try to make this
a little easier to see by getting each arguments on its
own line like that. Okay. So here is the arguments. Properties, animators
also very simple. The arguments, seems like
there is a lot of them and they are very complicated, but
they are really not. Okay. So here we are going
to make the thing big. So how long do I want
to take to make big? Again not really more than a
second. Maybe .6 or .7. These are blue numbers that I would
put in a constant struct and then play with to get my
animation to look the way I want. And I'm not gonna
delay this animation along, gonna start it right off. Really no un-animation options
necessary here. Just going to do the animation.
So in here is the closure and only thing I can change inside
here is those view properties. Okay, if I change anything
else, it will do it, but it's not gonna affect
the animation in any way. So what are the animations
we want to do here? Well we want all the face
up cards to get really big. I'm gonna do that with the
transform. Transform is really easy to make things big or
rotate them or whatever so I'm just going to say, my face
up cards views for each again, for each, oops. And inside
this 'for each' I'm just going to change the properties that
I want to change which is $0.transform=, and
I'm gonna get the affine transform identity which
is no rotation. And I'm gonna scale it by let's
say three times as big. So this is gonna make this view
three times its normal size. Let's just do just
this part for now. We'll do this completion
part in a little bit here. So I just wanna make sure that
this is working. So this is gonna force us to find a match
just to see if it's working, but I'm sure we can do that.
Okay, here we go. I need a match. No, 4 and 9. No, different
kinda 4, no good. There's a 9. Okay, so I'm gonna click this
card. They're gonna match. And hopefully they'll get
really big. And then they're gonna stop because we're not
finished with this animation. But let's see. Whoo, okay,
it worked that was easy. Now we want the next step of
the animation we want to make it small and
we're going to do its alpha so that gets small and fades away
at the same time. Okay so, we're going to do that with
this completion thing that I commented out, put that
back, and expand this thing. Remember this one has
the position not finished, but position which is either
dot end, dot start, or dot current.
And we don't really care what it is because we're
not gonna have animations that are gonna be jumping
on top of each other here. So it's not gonna
be a problem. So here we wanna do another
property animation. So I'm gonna copy and paste
this whole thing right here, put it in here. Okay. And
what are we doing with this property animation?
Very similar to the other one. But instead of the transform
being transform up to 3.0, I am going to make it really
small. Down to 0.1. And the same time I am going
to say alpha equals zero. Fully transparent. Now, This,
remember this is going to happen immediately. It's
going to set it to zero, but the user is going to see it,
over the course of .6. And actually, this one might want
to be longer, maybe .75 or something because remember I
went from Identity Transform to 3.0 now I'm going from 3.0
passed identify down to 0.1. So maybe I wanna give
it a little extra time, you see what I'm saying?
To go that extra distance, so it feels like it's coming out
and in at about the same time. But again, these are numbers
you tweak over time as you're working on stuff.
All right, so again, let's just see
if this is working. I have our completion
thing in there Okay, we gotta find the other
match again. A three, and then an eight, an ace.
No, that was ace of clubs and ace of spaded, see?
There's the ace of clubs. Is that the ace of clubs? No,
ace of spades. Ace of clubs. Okay, ready, here we go. I'm gonna click on this one,
now it should go up and then go down and
fade to alpha zero. Let's see. Perfect, okay.
It worked just fine. Now, once it goes down like that,
we want to remove this card, because it no longer can be
involved in the matches. So we are going to implement our
completion handler right here. And inside
the completion handler, we're not going to be doing
any view property animation, we're just going to
be doing cleanup. For example, I'm going to say
here for all these cards, so I'm going to have this for
loop again. But not this. For all these cards, I want to set
each of them to be isHidden, so I'm gonna hide the cards.
Now, I'm also gonna clean up a little bit.
Because I really don't like the idea of having these
views that are really small transforms. Okay, and
transparent, sitting around in my view hierarchy even
if they're hidden. So I'm going to go ahead and
make the alpha be back to 1. And I'm gonna go ahead and
make the transform, be back to being
the identity transform. So it's just a little clean up, I
just don't like to have messy, weird state views
lying around. They're hidden though,
so it's not really gonna hurt anything to have them
around there. But, for me, it's just a cleanliness thing,
to kinda clean up after your animation
a little bit. And since there it's hidden,
none of this is gonna show. The fact that I'm making
it transparent again and changing its transform,
it doesn't matter. Cuz this view is now
hidden from view. Okay, so that's good, we got that.
What's the next thing we need to do, what's the next one we
wanna do? We wanna make it so this game's a little harder.
I've got this game, and it's actually a little harder
than our other concentration game was because we have
to pay more attention. Because mismatches,
they flip over so fast that if I'm trying to
find a match, here, phew, got lucky with a match.
If I'm trying to find a match, it's harder to do. But I'm
gonna make it even harder by having these cards
be in motion. These cards are just gonna
be constantly moving around. So I'm gonna have to chase
after them to flip them over. And I'm gonna do that with
our dynamic animator. Cuz that's the kind of thing
a dynamic animator is good. Kinda set them out there and
it just kinda floats around. All right, so,
how are we gonna do that? Dynamic animator, let's go up
here. We're gonna start, we're gonna do the 3 steps, right?
Animator, behaviors, items, so let's start with the animator.
I'm gonna create a lazy var, which we're gonna call
animator. It's going to be a UIDynamicAnimator. And
the reference view for it, I'm gonna use the referenceView
constructor here. I'm gonna make myself.view,
right, the top level view of my view controller, I'm gonna
make that be my referenceView. Again, if you're writing an
app that had like subviews and stuff, it might well be
not be the top level. But this is a demo,
I don't have any other views. So that's what I'm gonna
have to do. All right, so that's gonna be my animator,
great. Now I need behaviors, so I'm gonna create
another lazy var here. I'm gonna call it my
collisionBehavior. It's gonna be of type
UICollisionBehavior. And I'm gonna initialize
this one with a closure. We talked about
that in lecture but we've never actually
done it I don't think. So how do I initialize it with
a closure? It's really easy, I just create the behavior.
I create a UICollisionBehavior here. No arguments to its
initializier, configure it. So the only thing I wanna do
with this collision behavior is have the edges of my
referenceView keep my cards in. The only thing the cards
are gonna bounce off is each other and the edges.
So, I'm gonna do this translateReferenceBoundsIntoB-
oundary = true. It's a kinda cheap, quick way
to get a collision boundary. Then I'm gonna add it to my
animator, addBehavior, and then I'm gonna return behavior
from the closure. This returns it from the closure, which
assigns it to this var, and it's all lazy. We see how
we use a closure there, really convenient. Now we
have what do we have here? AddBehavior, add the behavior
to the animator, all right? So
now we have this behavior. We need to do step 3,
add the items to it. Here's where we're creating
all of our card views. Let's just say right here
collisionBehavior.addItem, cardView. Now that card view
is instantly going to respect that boundary and start
bouncing into other cards. But of course we haven't moved the
card, it's not moving, so we need to do that. We're gonna
do that with a pushBehavior. So I'm gonna let push
= UIPushBehavior. Now pushBehavior's initializer
here takes the items that you wanna push. So So this would only be this one
cardView right here. I could create a push behavior that
puts all the cards in there. But then they would all get
pushed in the same direction, I don't want that.
And then the mode, again, can be continuous or,
in our case, instantaneous. We're just gonna give the card
a push and then we are done. And since it's instantaneous, we're gonna wanna
clean up after it. So let's give it an angle,
so I'm gonna give it a push angle, which is a random angle
between 0 and 2 pi. This is in radians, so I am gonna say
2 times CGFloat.pi and it's gonna be random. So I am
gonna need to use arch4random. I added a little arch4random
to CGFloat as well. We added it to int, but you
already knew how to do that, but I added one for float. And then the magnitude, I don't
really wanna push it 0. So I'm gonna do a magnitude of
let's say at least 1.0, right? But then I'm gonna add
a certain amount extra, let's say 2.0 arc4random. So we're gonna have the magnitude
and this has to be a CGFloat, right? This is a double, so
we'll go CGFloat of all this. Oops, CGFloat(1.0),
CGFloat(2.0), random. Probably don't need
both of those CGFloats. But anyway, so we create a random.
So I'm creating a random magnitude between 1 and 3.
Pushing a random amount there. Now I just need to say to the
animator. Add this behavior, add this pushBehavior.
And as soon as I add it to the animator,
it's going to push its items, which is just that one
cardView. Now again, we know that this is bad because it's
done now and it's never going to get cleaned up. So
I'm gonna use the push.action, okay, to remove it from the
animator by just saying push. Tell me your dynamicAnimator
if you have one, and remove yourself from it. And we know that this causes
a memory cycle, cuz this push right here is being kept in
memory by this closure. And of course the push's action is
pointing to the closure, so they're keeping each
other in memory. So we're gonna get rid of this
by just saying unowned.push. And yes I could've said weak
push and then had an optional chain here, and maybe even
an exclamation point here. But if I'm gonna do
exclamation point, you might as well
make it unowned, because you're unwrapping
it anyway. All right, so let's see what happened. So all we did here is we created
an animator, a collision behavior, and a push behavior.
Let's see what happens, see if it just animates.
Woo-hoo, we did it Now, I'm not quite getting
what I want here though. One thing is look how
quickly it settles down. It kind of settles down
too easy, too quickly. That's gonna make it too
easy to play this game. I wanted those cards
moving a little more. The other thing is I don't
think I want them rotated. It's kind of fun to have
them rotate actually. But when you flip the card,
for example, it doesn't flip along
the axis of the drawing. It flips along the axis of
the view. So watch this flip, you see how it's kinda
flipping around the corner? Maybe that's okay, but
I'm gonna decide it's not. Because I also wanna show
you how to do dynamic item behavior. Also, the reason
that it's slowing down so much is there's not enough
elasticity in the collisions. I want those collisions
bouncing off and keeping the energy up.
So let's fix, and we do that with another kind
of behavior, kind of like our collision behavior.
It is a itemBehavior, it's a UIDynamicItemBehaviour.
And I'm gonna do that with a closure as well. So, we just
create the behavior here, oops, let behavior=
UIDynamicItemBehaviour. And now we'll just
configure this behavior. So I want
allowsRotation = false. I don't want those
things rotating around. I want Elasticity and
see and there's a lot, you're going to have to look
in the documentation to see all of the incredible things
that you can do here. But I want elasticity 1.0 means
that collisions don't lose any energy or gain energy. if I
set this to an elasticity 1.1, they would gain
a little energy. Those things start going
faster and faster and faster, forget it. But if I set it to
0.9, they're gonna slow down. Not as slowed down as much
as they are now, but so 1.0 is as kinda the most
elasticity I can give it and not run into an accelerating
situation. And also, I'm gonna set, I'm not sure what the
default of this one is, but I'm gonna set resistance,
which is how much it resists forces being applied on it.
And I'm gonna set that to 0, I don't want any resistance.
I want it to be kind of free flowing here in outer space
not resisting anything. So let's add this to
the animator, add behavior, and return the behavior. Okay,
now is that all we need to do? No, because if we don't
add any items to this item behavior, then it's
not going to do anything. So same way that we added
the cardView to the collision behavior, we have to add
it to the item behavior. And as soon as we do that, as
soon as we add this thing in, it's gonna be having all those
settings allows rotation and all that business. So
let's see if that makes things better. Okay,
it did make it better. Okay, it's still slowing down,
which I kind of want a little bit because I want
the person to have a chance to by they're off.
They're going too fast, you're just gonna be
grabbing after them and then you'll never remember
where the other ones went. So I do want it to slow
down a little bit. Maybe not all the way to here,
I can play with this. Actually have a kind of
an idea for how to keep things in motion as the game
goes on, but still give people a chance by letting it slow
down. But before we do that, I wanna talk a little
bit about that creating a UIDynamicBehavior subclass,
where we combine other ones. Because look, we've got three
different behaviors here. We've got the collision
behavior, we got the item behavior,
we got the push behavior. And we're having to do all three
different pieces of code for that in our view controller.
It'd be a lot nicer if we had another behavior called the
card behavior. And it had all those as part of it. And then
we just add the card the card behavior, and then we'd get
the collision and the item and the push automatically.
So that's what we're gonna do. Let's go up here
to File>New>File. I'm gonna create
a Cocoa Touch Class, a UIDynamicBehavior as
a Cocoa Touch Class. Here it is subclass
of UIDynamicBehavior. I'm gonna call it
CardBehavior. I'm gonna put it where all the rest of my files
are there, don't forget that. Here it is, CardBehavior, a
subclass of UIDynamicBehavior. And all I'm gonna do is go
over here and grab all this stuff, and take it outta here
and put it over here. Okay, so I'm just putting those
behaviors over there, even the push. I'm gonna take
the push out of here as well, put it over here. The push I'm gonna put
in a little function, a private func called push,
which pushes an item. This can all work on UIDynamicItem,
I have UIViews but there's nothing in my code
that's specific to UIView, it's all just dynamic items
being animated here. So we'll put all that push stuff
in there. Now we're gonna have to fix some of these little
copy and paste problems. But this is essentially
how you create your own dynamic behaviors by doing
these. But these have to be added as children and we add
them as children in an init. So 99.9% of the time, when you
create a dynamic behavior, you're gonna override
init with no argument. You're gonna call super.init,
and then you're gonna add your children. So I've got
my collision behavior, I'm gonna add, I've got my
item behavior, I'm gonna add. Now, I can't really add
my push behavior here, because the push behaviors,
I need to know the item. So I can't quite do that yet. So
and obviously I don't need to be adding them to
animator right in here, cuz there somebody's gonna
add me to an animator and that's how these things will all get put in an animator.
Same thing here. So how am I gonna deal with
this fact of push? Well, I'm also gonna add a func
add item which adds an item, a UIDynamicItem.
And when I add it, I'm gonna add it to
all my children. So I'm gonna add it to
collisionBehavior.addItem, I'm gonna add it to
my itemBehavior, And I'm gonna push, so
I'm gonna push the item too, which is essentially adding
it to the push behavior here. So let's fix this one up, this
needs to be item not cardView right here you see
that argument, right? The push action here,
notice that we're removing the behavior directly from
the dynamic animator. Actually what I wanna do here
instead is make it a child, so I'm gonna add a child behavior
which is the push. And then when I remove, instead
of removing behavior directly from the animator, I'm gonna
remove my child behavior. See, now there's a little
thing going on here, it's gonna complain
once it compiles here. Implicitly use of 'self' in
closure, use 'self'. In fact, I'm gonna do this if I can
by just typing self., and this is now capturing self, which is the dynamic behavior.
Yikes, okay, we don't wanna do that because the dynamic
behavior definitely has a pointer to this closure,
because it has a pointer to its own child behaviors of one
which is the push behavior. And the push behavior
points to it. So I'm gonna get rid of
this by saying, weak self. Okay, phew, broke that
thing. I still need unowned push because I'm also passing
push right there. Now I don't wanna do unowned self there,
because just in case for some reason this whole behavior
got removed from the heap, I don't wanna be
crashing here. So I'm just gonna do a weak
to be a little bit safe. It's not the same case where I
know for a fact that this push has to be in the heap cuz
I wouldn't have got here. That's not necessarily true of
self. So we are push there so, now that we've collected
all of our code right here, actually I'm gonna
add one more thing, cuz we haven't seen this
as convenient init. Okay, I'm gonna add a
convenience initializer here, that let's me specify the
animator that I wanna be in. I just wanna show
what it looks like to create a convenience init.
And all you do when you create a convenience init is
you can all a self.init only on yourself, then you can
do what you wanna do. In this case, I'm just going
to tell that animator to add behavior myself. See how
that's like a convenience for me to be able to create
one of these and have it automatically added.
So let's use that convenience initiator, initializer back
in our view controller here, actually we can go back.
Here it is, and instead of adding each
item to both collision and item, we just add it
to a cardBehavior, which we'll have to create. So
I'm gonna have cardBehavior, add the item to cardView, and
then I'm gonna create a var up here, a lazy var. It probably
doesn't even need to be lazy, yeah, it probably does.
Lazy var, which I'm gonna call cardBehavior, which is just
gonna be a cardBehavior. And I'm gonna use my nice
convenience initializer to say in my animator.
Okay, so this really cleaned up our code here in
our view controller. In fact, there's only two lines of
code at all that have to do with our dynamic
animation in this thing. We've moved it all to that
nice thing. Let's make sure we didn't break anything by
doing that. We didn't. Okay, it's still working. And we can
still flip out cards over. Those are separate animations
going on over there. Now, I wanna do a couple
of other things here. One is ,when you pick
a card I want it to stop. I want it to stop being
animated. You picked a card you get to take a breath,
and look at your card. And then, if you flip the card
back over or if it match it, or if doesn't match and
it gets flipped over for you, then I'm gonna give it a push.
Because see how everything's stopping, so now,
choosing cards, we'll start pushing them a little bit and
getting them going again. So, yeah, you can wait for the
whole thing to slow down and stop and then you can pick a
card. But once you pick them, they're gonna all
move around again and be shuffled up again,
make it hard on you. So that's seemed like a good compromise to me between
constant movement and all sitting completely
still like this. So how am I gonna do that? Well
that's super easy to do now because I have that one
behavior. And if I remove an item from that behavior,
it's going to automatically stop animating. Because
nothing will be behaving. There will be no behaviors
operating on it. Okay, so how do I do that? Well, I'm just gonna do this right
in my flip card right here. As soon as I get a touch
that's gonna flip a card over, I am just gonna say
cardBehavior, not that. CardBehavior with
a lower case C, cardBehavior remove
this chosenCard. Okay, but I do wanna add it
back to the behaviors. When do I want to add it back
to the behaviors? Either when, I didn't add remove item,
let's make sure we do that, sorry. Where is our behavior?
Yeah, sorry, we have add item, we need
remove item as well, okay. And removeItem is exactly the same
kind of thing, except for that we are removing things
from all of the behaviors. Remove, Sorry about that,
forgot about that. Remove, and, we don't have
to remove the push because we automatically
remove the push as soon as it happens, right? Here's where
we're removing the push. So since we removed the entire
push behavior we don't have to remove the items from it.
So there's removeItem, sorry about that. Okay, clear,
all right let's go back to our thing here. So now we can do
a removeItem, but we wanna put them back in, when do we
wanna put them back in? Well if you just flipped the
card face down, we wanna put it back in then, or if the two
cards gets flipped down for you automatically because
you had a mismatch, then we also wanna do that. So, let's
go put that, and we're just gonna put add item to put
them back in those cases. So, where are those in here?
Let's see if I can find them. Okay, so here is the one where
two cards don't match and we're automatically flipping
it face down right here, so I'm just gonna have
this cardBehavior, add item, the card view.
Okay, let's put it back in. It got flipped face
down automatically, and then here's the else,
oops and of course self. Does that cause a memory
cycle? No because these are animation system closures.
We're okay, all right? So here, this is in the case
where there weren't two cards. There was only one card. And
if you flipped the card face down, if the chosenCardView, it's face down, so
not face up. So you pick the card, it's face
up then you picked it again, it went face down, then here I
need to have the CardBehavior right on itself there, because
I'm not in a closure add item, put that cardView back. Now what about cases
were the cards match? Don't need to put them back
because they go away, right? We remove the areas hidden, so there's no reason to
put them back, right? And it's not cardVew,
it's chosenCardView. Okay, so let's see if this works.
Oops cardBehavior, it's right in it's in the completion
handler. This is in a closure, it's in the completion handler
up here of this initial flip. So does this cause...
every time you have to, any time it says this to you
and you put self in there you wanna go, hmm, does
this cause a memory cycle? No, it doesn't here,
we're okay. It's just a completion handler
closure, we're fine. Okay, so now when I click one,
if I can get one, there it is. It stopped, see,
it's not being bounced into or moving in any way.
And now if I pick another one, they don't match. Now, they
both get a push. Because every time I add an item back to my
cardBehavior, it gets a push that's kind of cool,
kind of a nice little feature. Same thing here, oop they
don't match they got pushed. Okay, these don't match
they get pushed and hopefully if we find two
that match somewhere, somebody help me how do
they know where one is. There we go, we gotta match,
okay, anyhow. So this is kinda nice,
it plays kinda nicer. But there's still some things I
don't really like about this. One is, I really don't like
things on the edges so much. I don't mind if things
stop, but I'd really rather they be towards the middle.
And so, I have a little fix for that, which is over
here in my cardBehavior here. When I push, instead of
pushing at a random angle, instead, I'm going to push,
and we don't have time for me to type it all in. I'm
gonna push towards the center. So depending on where the card
is in its reference bounds, the reference views bounds, I'm gonna push towards the
center. So this is the process when you do animation of kind
of tuning your animation. Seeing what makes sense,
what doesn't make sense. So this is a nice tuning thing
to push towards the center. Another way that we can
tune our animations here is to slow them way down.
Because when they're going so quickly sometimes they're
not quite working and we can't really tell. So let's
go back and slow down a couple of our animations to see how
they're doing. So, for example let's load down our animations
of the matching cards. So when the cards match that to right
here remember we're scaling it up to three times and we scale
it back down. And we had out alpha to zero. So instead
of this taking, you know, about a little half a second,
three quarters of a second, let's slow it way down to
take six total seconds, three seconds. For each part of the animation
and see if our app is working well doing that, so
we're gonna find any problems. Okay, it's gonna require me to
find a match. Let's see we got here search around from cards
til we can find, I think, nope, no well, there we go.
So it's going so slow, but look what happens if I
click on other cards. They're still able to match, in fact I can select
more than two and then they disappear.
Okay why do those disappear? Well that's because this
second animation that comes along sets their
alpha to zero, picks them up along the way
while this long animation is happening. So we really need
to more careful when we're doing this second half
especially of this animation, that we're only working on the
cards that were involved in the match. And there's a
couple of ways we can do that, one for example here where
we're deciding whether we wanna do this animation or not
we could just make sure that are faceUpcardViews count is
always less than 2. Because if we already have two matching
cards that are expanding and growing out, then we obviously
can't match anymore. So that would kind
of work except for that you can imagine
if I had a match and the cards are expanding out
that it might actually want to start working on my next pair.
And so for that to work, we really wanna have those
two matching cards not really count as face up cards at all.
And we can do that up here. Here's where we decide what
a face up card is. Currently it's a card that is face up
and obviously is not hidden. And we could just enhance this
a little bit here because we know that those cards that are
expanding are either scaled up to 3.0 in their transform, or
their alpha is not 1, right? It's 0, in fact So we can
just put those on here. So lets say a face up card also
has to have a transformed that does not equal the identity
transform scaled by 3.0, 3.0. By the way you can
see the terror and danger of blue numbers because
if I were to ever change this number and forget to
change the same thing here, then this would not work.
My animation would be out of sync with what I consider to
be a face-up card. So this is where we'd wanna create
a struct, a private struct and use statics to have those be
constants. So we also want to make sure that our alpha
here is equal to one, in other words, we're only
going to consider card face up if it's fully visible. The
last thing we're going to need to do here also though is when
we go into our animations, notice that we look for
which cards are face up. By calling this self.face
up card views, all right? And that is this thing right
here. Well this is dynamically always calculating the cards
that are face up, so if we have an animation
here and it starts off and is going and then it tries to
do its second part, it looks for the face drop cards again.
When in fact, we just want this entire animation to apply
to the original two cards that were chosen. So we can
capture that by just having a little local variable. Here
I am going to call it cards to animate that equal
the face of cards at the time we start the animation. Okay
so we are sort of capturing them here. And then going
through and using this. So everywhere we're re-looking
up these face up cards instead we want to use the cards
to animate. So let's go down here, this one,
this one, this one, I'm just searching and replacing here
all of those cards to animate. So let's try all of that and
see if that has made it so that our nice, winning
animation doesn't have that same problem. So, I've gotta
find our matches again, whoops, let's see.
There we go, there's a match. Now I'm gonna click on other
cards and it's able to match them. And that long animation
was still continuing. So that's much better UI. Now of
course we're not gonna have our matching animation take so
long, but by making it take so long we are able to find that
problem. So let's put that back to what it was.
I think we had like .6 and .75. Let's take another
animation and slow it down, how about the animation where
the cards don't match and we flip them back over? Okay, that's this animation
of it flipping over and then here is where it
flips the two mismatching cards over. So
lets slow both of those down. So that one down and
slow this one down here. This was the matching
animation. I want the, the original flip
animation there. All right, so we click, okay our
original flip animation. Okay, it's going real slow,
nothing seems to be a problem. Let's try another one
that goes slow. And they both, okay.
It all looks okay. Let's try clicking a little
faster. How about this one and this one? All right,
they both go slow. Wow, what's happening? Okay,
what happened to that ten, that second card? It kind of,
it didn't flip over. It kind of jumped, and
then it flew around. Let's try it again to see
what's happening here. Let's try this one and
this one, here we go, whoa. Okay, so something
is clearly wrong here. So what is happening here that's
making that card go so wacky? Well, what's happening here is
both the original card, that we flipped up, and the second
card are both trying to flip the two cards face down. Okay,
and so those two transforms, those two modifications
here via transition, they're both trying to operate
on the cards at the same time. And when you have something in
the middle of transforming and another transform comes along, it's basically messing up
the whole transform. Now, an easy fix for this one too
which is let's just always let the latest card
that was chosen control the animation. That
way they'll never interfere with each other because when
the second one comes along and chooses it gets to do the
animation. So let's do that by just creating a little var
here, to keep track of our last chosen card view which
will be a playing card view of course. And it'll start out
nil, that's fine cuz we don't have a last chosen card
view when we start out. And so every time that we go
through and choose a card, okay right here,
this is where we choose card. We're just gonna remember
that our lastChosenCardView = chosenCardView. And then when
we get down here to this animation of them closing down
here, we're only gonna do this if the chosenCardView
equals the last chosen card view. So, that way, this animation
right here can only happen, can only be controlled,
by the last card. And they won't interfere
with each other. So we need a self right here and
let's try this. All right so here's this one,
it started animating, start this one and it worked. Okay it let the first one
finish its animation and then it didn't do it, didn't
do the flip and waited for the second one to do it.
Okay, so these are the kind of things when
you're tuning your animation, that slowing them down
will help you find these overlapped things.
And you might think well, these cards are flipping so fast, users would never
be able to do this, but they absolutely can. Users
tend to click, click, click, click, click. Oops, they're
clicking quickly to try and make things happen, so you
wanna be prepared for that. So I'm gonna put these back
to where they were. What do I have,
I don't know, 0.5 or something, 0.6,
something like that. Okay so, here we got
this guy and that guy. Let's see if we can find
a match here somewhere. This one. No, it's hard to
find a match at this game. It's quite a difficult game. I found that I'm not really
very good at it. There's that jack, I think that one
there. Yeah, there's a match. No that moved.
King over here? There we, that was king of clubs, or
king of spades. All right, well anyway, there we go,
there's a match. Alright, I believe that's all I have
time to show you. I think I've got through most of the stuff
I want to show you, so your homework is just to animate
your set. In very predefined ways. So you'll be using these
exact same three mechanisms. Transitions, view properties,
and the dynamic animator.
All right? I'll see you all next week.
>> For more, please visit
us at stanford.edu.