WWDC2014 Session 236

Transcript

X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
>> Alright.
Good morning, still, right?
Sorry, I've been up
here a little while.
I forgot what time it is.
Welcome to Building
Interruptible
and Responsive Interactions.
So Andy and I want to
spend some time talking
about how you can build
interactions into your apps
that are really fluid and
make the user interaction
with the content in your app
really smooth and natural
and avoid any sort
of jarring changes
as you transition
between things.
And if you were just here
for the Scroll View session,
I'm going to start
out with something
that probably won't
be too surprising
and show you a scroll view, just
to give you an idea of the kind
of things that we're
talking about here.
So here I've got a nice bird
that's in a UIScrollView,
and I'm zooming and
scrolling and stuff.
The thing I want to draw your
attention to is the transitions
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
The thing I want to draw your
attention to is the transitions
between the user dragging around
in a scroll view and letting go
and how animations start
and then grabbing again
as the animations
are in progress.
So I can zoom in on this
bird and I can drag it down
and it'll bounce
against the edge.
But if I keep grabbing
it, there's never a time
where I can't start scrolling.
It always works.
And I can scroll around and it's
all a very smooth transition.
And that's what we want to talk
about this morning
is transitions.
So what kinds of transitions?
Well, three transitions
in particular.
Number one: transitioning from
gestures into animations and how
to do that really,
really smoothly.
Number two: transitioning from
animations to other animations.
And I'm sure you've already
guessed what number three is:
transitioning from animations
back into gesture control.
So the key to making all
of these interactions really
smooth is just making sure
that the transitions
between these states are
as seamless as possible.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And so let's start out by
talking about transitioning
from your gestures
into your animations.
So it's often the case
that you're doing something
like dragging content around
using a PanGestureRecognizer.
And you're maybe having it
track your finger directly
because that's a really great
way on a multi-touch device
to optimize gesture
interactions.
But then when you
lift, you might want
to start some animations,
something like what a scroll
view does, to do a deceleration
or something of that nature.
And you want that
transition point
to be as smooth as you can.
So let's take a look at two
possible ways to do this.
I've got this on loop, so I'm
just going to leave it up here
for like 15 minutes and
we can just stare at it
for the rest of the day.
The bottom one is looking a
lot smoother than that top one.
We've got two fingers that
are dragging these boxes.
This one comes off
when the finger lifts
with the same velocity and it
comes to rest nice and smoothly.
The top one, as soon as I lift
my finger it snaps to a stop
and then slowly starts up
and then comes to rest.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and then slowly starts up
and then comes to rest.
So we want to figure out
a way that we can make all
of our transitions from gestures
to animations behave
like that bottom box.
And the first part to making
this happen is to figure
out what velocity we want
to start our animation at.
So number-one concern:
compute velocity.
Now, if you're using some of the
standard UI gesture recognizers,
this is really easy to do
because it's actually
already done for you.
So if you're using a
UIPanGestureRecognizer,
you've got this velocity
and view method
and you can just pass in a view
and you'll get back the velocity
the panning was happening at
and whatever coordinate
space you need
to start your animation.
So that's really easy.
If you're using a pinch or a
rotation gesture recognizer,
you get the exact same kind of
thing, just for whatever it is
that you're computing.
In the case of
PinchGestureRecognizer,
you're getting the
velocity of the scale.
And for the rotation you're
getting angular velocity
for that rotation.
So computing is really
easy if you're using one
of these standard
gesture recognizers.
If you're not, computing
velocity is still a pretty
straightforward thing to do.
Velocity is just
distance over time,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Velocity is just
distance over time,
so if you just keep accumulating
your distance and your times
and keeping track of that,
you can compute the velocity
that you should start yourself
for whatever interaction
you're interested in.
So that part's pretty
straightforward.
The interesting part is,
how do we start our
animation at that velocity?
So let's take a look at the
standard UIKitAnimation method.
We've got animateWithDuration,
delay, options,
animations, completion.
So there's no velocity in there.
So that's not going to
be obviously an easy way
to go about doing this.
But happily last year with
iOS 7 we introduced a new more
expressive version
of this method
that had two new
parameters in it.
We've got usingSpringWithDamping
and initialSpringVelocity.
So that is looking a
little more promising.
So let's focus on that
initialSpringVelocity.
This is the thing that is going
to let us pass the velocity
out of our gesture recognizer
or wherever we computed it
and start an animation
that feels
like it's coming off the
finger really, really smoothly.
So how do we compute this
initialSpringVelocity?
Do we just take that velocity
that we had and pass it in?
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Do we just take that velocity
that we had and pass it in?
Obviously if the answer was yes,
I wouldn't really be
talking about this.
So we're probably going to
have to do something else.
So let's take a look
at an example,
assuming that we were panning
some object and we want
to start having it decelerate
just along the x-axis
for simplicity's sake,
and see what we do.
So we've got a box.
We were panning that around.
It's at point 50 right now, x
coordinate 50, and we're going
to have it animate over
to the right side there
to x coordinate 150.
Now, let's assume
that we calculated
that our horizontal velocity
was about 50 points per second,
so that's where we
start this animation at.
To figure out what
velocity we want to pass
in for the
initialSpringVelocity,
we actually have to compute
a normalized velocity.
The coordinate space that
this method is expecting
to get the velocity in is a
normalized coordinate space.
And we want to normalize it
based on the total distance
that this object is going to
travel during the animation.
So first we have to
calculate that distance.
So we've got our
destination value, that's 150.
We want to subtract from
that the starting point.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
We want to subtract from
that the starting point.
So we subtract off 50.
And we get a total distance
traveled of 100 points.
So then to figure out
the velocity to pass in,
we just normalize the
velocity we want by dividing
by that total distance.
So we take our 50
points per second,
divide by the total
distance traveled of 100,
and we get an initial
velocity of .5.
That .5 we just passed
in, then, to the method
that we just took a look at
as our initialSpringVelocity.
So UIViewAnimateWithDuration,
we can give it a time,
how long it's going to take,
delays, all those other things
that you normally
do, and just pass
in that additional
velocity parameter
and we'll get a really
nice effect.
So then if we take a look
at what that's going to end
up doing, we can see that
if we drag this box around,
we get it coming off our
finger really smoothly.
And, additionally, we
get a nice bounce there
at the end as it comes to rest.
That bounce was the
other parameter we saw
and you can tweak that to
get a different kind of feel,
whatever feels good
in your apps.
You can get nice springy
animations as they come to rest
with a smooth velocity
coming off your finger.
So that's what you can do with
standard UIView animations.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So that's what you can do with
standard UIView animations.
But there's other
options, as well.
So let's say maybe you wanted
to do something a little more
advanced and have more types
of interactions with
other views on screen
or more complicated
decelerations
than just a spring.
So also in iOS 7, we
introduced a class
called UIDynamicAnimator.
And this is another great way
to go about getting animations
that feel like they
come off your finger
at the exact velocity that
you were interacting with.
So let's look at how we'd set
that up in a UIViewController
in order to get some animations
going with the DynamicAnimator.
So first off we would
create a UIDynamicAnimator.
Now I'm doing this in
SWF, so there's a couple
of interesting things
we can talk
about while we go through this.
Here -- I'm assuming this is a
property on our view controller
and I've typed it as a
UIDynamicAnimator question mark.
And I actually just
made that an equal sign.
It should be a colon.
I literally changed this on
the stage before I got up here
because I thought it was wrong
and now I just realized
while I'm
up here I changed it
to the wrong thing.
So don't write code in Keynote.
It's a bad idea.
I'm trying to declare
a DynamicAnimator
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
I'm trying to declare
a DynamicAnimator
of type UIDynamicAnimator
optional,
an optional UIDynamicAnimator,
because we're going to go
and initialize this later.
The reason that this one
needs to be optional here is
because UIDynamicAnimator,
when you create one,
takes a UI view at
creation time.
And you don't have a UI
view at creation time
of UIViewController because
you want view controllers
to load their views lazily.
So we want to start
out with this being nil
and then initialize it later.
So, anyway, we've declared
our property for it.
We'll go and set it
up a little bit later.
Now we've got our
dynamicItemBehavior,
which is a class
that participates
in this UIDynamic system and
this is where we're going
to attach views that we want to
have dynamics associated with.
So we create a
UIDynamicItemBehavior
and we can initialize
this here as a constant
because we don't need
to provide any views
at the time we create it.
It lets us add or
remove views over time,
so we'll create it
ahead of time,
create our
UIDynamicItemBehavior.
And now we'll override the
viewDidLoad method because,
when a view controller goes to
load a view, it's going to load
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
when a view controller goes to
load a view, it's going to load
up all the view bits and then
there's a good place to go
and add additional
initialization
after the view's
loaded in viewDidLoad.
So we'll call super just to
make sure that it gets a chance
to do any work it
was going to do.
And then we can go and set up
the rest of our dynamic system.
So we have that optional
UIDynamicAnimator.
This is a great place
to initialize it
because now we know
we have a view.
So we can create our
UIDynamicAnimator.
We have our view now,
so we can pass it in.
We also want to set a
couple of properties
on the dynamic animator, just
so that as we set velocities
and get some deceleration
animations,
things will come
to rest smoothly.
So we're going to
set our resistance
and angular resistance
to a value I just
picked randomly of 3.
You can pick different values
and get different feels.
And then, finally, we're
going to add that behavior.
We created the
dynamicItemBehavior earlier.
We're going to associate it now
with the DynamicAnimator
now that we have it.
And so that's pretty
much all there is
for setting up the system.
So then we have to figure
out how to transfer velocity
into this dynamic
system from our gestures
because that's really
what this is all about.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
because that's really
what this is all about.
So let's take a look
at how we do that.
The first thing we want to do is
find out what view we're going
to be associating
this velocity in.
So we've got the
panGestureRecognizer.
Let's assume that it's
attached to some view
that we're dragging around.
We'll get the target view.
Now this is what
you would often do
in a UIGestureRecognizer target
action method, so I'm assuming
that we're doing that here.
So let's do the normal switch
statement that you would see
within a target action method
for a gesture recognizer.
So we'll get the
panGestureRecognizer state
and switch on that.
Now, right now we're just
talking about transitioning
from the gesture to an
animation, so we won't worry
about any state other
than ended because that's
when you perform
this transition.
So let's go and deal
with the ended case.
So now we have to get the
velocity that we want to move
into the dynamic system,
so we'll ask the
panGestureRecognizer
for the velocity.
And the view usually that you'll
be doing here, if we're dragging
around the view it was attached
to, we want to get the velocity
in that view's super view
because that's the
coordinate space
that the view's geometry
is going to be in.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that the view's geometry
is going to be in.
So we'll get the velocity
in that target view's
superview's coordinate space.
And then we'll just ask
the dynamicItemBehavior
to add some linear velocity.
Adding linear velocity will
just increase the velocity
in the dynamic system
of the object
by whatever the current
velocity was
that we were dragging
it around by.
So we can add that
into the system
and then it'll smoothly
decelerate
from whatever the
gesture was doing.
So, again, now if we do look
at some boxes and view this,
we can see that as
we drag these things
around they decelerate smoothly.
But because this is dynamics,
they can also interact
with one another.
So as they collide,
they're going
to start doing other
interesting things.
So this is a great way
to get more advanced behaviors
while still preserving these
really smooth transitions.
So we've talked about two
ways to go about doing this.
We've got animateWithDuration.
Now, there's different reasons
why you might choose one
or the other, and there's no one
right answer for all situations.
animateWithDuration, one of the
nice properties of doing this is
that it creates server-side
animations.
Now, what that means
is that the animations
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, what that means
is that the animations
that are created using those
spring velocities that we talked
about at the beginning, those
are actually going to execute
in another process out of the
context of your application.
So even if you end up doing
some work in your application
that maybe blocks your main
thread for a little while,
the other render server is
running at a higher priority.
So it's going to be optimized
to make sure that we try not
to drop frames on
that animation,
even if your app has a
lot of heavy work going
on in the main thread.
So it becomes easier to
maintain smooth animations
if you just offload it onto
the render server by using
that animateWithDuration method.
Now, of course, the
other option we talked
about is UIKitDynamics,
so UIDynamicAnimator.
Now, one of the big
benefits of this is
that you get even more
advanced interactions.
And as we'll see a little
bit later when we talk
about more transitions, some of
the transitions can be easier
when you're using
a UIDynamicAnimator
than when you're using the
server-side animations,
because with UIDynamicAnimator
everything is happening
in your process, in
your address space.
Now, the downside of that is
it means it is going to rely
on your main thread
turning often enough
that the DynamicAnimator can
update the dynamic system
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that the DynamicAnimator can
update the dynamic system
and get everything
committed for the next frame.
So if your app happens to do
a bunch of work or, you know,
is taking more time
to get something done,
it's easier to drop frames
if you're not being careful.
So UIDynamicAnimator is a
very powerful way to do it.
You just have to be a little
bit more careful to make sure
that you're optimizing your app
to avoid dropping any frames.
Now, there are other options
that are a little
bit more advanced.
And, in fact, the thing
that UIDynamicAnimator is
built on is CADisplayLink.
Now CADisplayLink is a class
that calls you back
once every frame.
So a frame is going to get
rendered, you get called back
to go update your app in
whatever way you want.
Now, that will give you 60 Hertz
callbacks and so you can try
and update your animations
at 60 Hertz.
That's exactly how
DynamicAnimator does it.
So if you have some
really custom thing
that you can't accomplish with
one of these other technologies,
you could build your
own animations
to do things yourself.
Now, of course, this is an
even more advanced thing.
We're not going to go into
exactly how you would go
about writing arbitrary
animations.
If you have an idea of
something you might want to do,
you probably can go
build it yourself.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
you probably can go
build it yourself.
But some of you that are
out there may be thinking,
"Well, wait, hang on.
There's another option.
Why aren't we talking
about NSTimer?"
This was a common thing
before CADisplayLink existed.
You just set NSTimer, set
it callback at 60 Hertz,
and drive animations off that.
Well, to understand why we're
not going to talk about that
as a really viable
option, let's take a look
at a timeline showing what
happens over time for NSTimer,
CADisplayLink, and all
of the display frames
that are coming on screen.
So here I've got
some vertical bars.
Each one of those
represents a display frame.
Now let's take a look at
what CADisplayLink does
within each of those gaps.
So because we're rendering
at 60 Hertz on iOS,
each gap there represents
about 16 milliseconds.
It's a little bit more than
that, but that's roughly right.
So that's how much time you
have in between any two frames.
Now CADisplayLink, as I said,
is going to call you
back once per frame.
And the place where it calls
you back is going to try and be
as tight as possible to where
the frames are happening.
So you're going to
get these green dots;
that's callback times that
are associated roughly
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that's callback times that
are associated roughly
with where the frames happen.
So what happens with NSTimer?
Well, it depends.
NSTimer, you might
get them there.
Maybe you'll get them
at the beginning.
Maybe you'll get them
closer to the end.
But let's say you get
them in the middle.
You actually aren't really sure
where you're going to get them
because when you get them
depends on when you happen
to set up the NSTimer.
NSTimer isn't synchronized
with the display in any way,
so when you get the
callbacks will depend exactly
on when you set up the timer.
If you set it up for
a 60 Hertz interval,
it'll be 16 milliseconds
from whenever you set it up,
depending on where in
the frame that was.
So it could be anywhere.
It's hard to know.
Now, why is that a problem?
Well, looking at how
much time we have now,
we had 16 milliseconds
for this whole frame.
With NSTimer there we've
got about 8 milliseconds
from the time that it got
called until the next frame,
assuming that it happened to
be set up right in the middle.
Now, even then, you might
not see why exactly that's
a problem.
I mean, so it's eight
milliseconds.
You can probably get
done in less than eight,
or if you can't, then alright,
you're always in the next frame.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
or if you can't, then alright,
you're always in the next frame.
Well, the trouble
comes in the places
where maybe you aren't
always either
in the current frame
or the next frame.
So let's say that it actually
takes you about 8 milliseconds
to set up your frame and get it
committed to the render server.
So if we look at our second
frame here out of these
that I have listed, let's say
that the first time we get it
done in eight milliseconds,
but that happens to
miss being rendered
to the screen for this frame.
So it goes over and
it'll come in, you know,
another 16 milliseconds later.
So we missed that frame.
Now the trouble is that
maybe the next time we happen
to get things done a little bit
faster and do make that frame.
What that's effectively going to
end up meaning is that the frame
where we missed will really
just never display to the user.
It'll be as if it
just disappeared.
And that's going to end up
looking like a dropped frame.
So we're rendering
within eight milliseconds.
It's only taking us eight, and
we have 16 to get things done,
so it seems like we should never
drop a frame because we're well
under the total frame time.
But yet we just dropped a frame
because we were right
on that border.
With CADisplayLink, if it was
taking us eight milliseconds
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
With CADisplayLink, if it was
taking us eight milliseconds
because we started close to
the beginning, we still have
that extra eight-millisecond
buffer time every time,
so there's no way we're
going to miss that frame.
Now, if you do take 16
milliseconds you might get back
into that same situation, but if
it's taking you 16 milliseconds
to prepare a frame, you're
basically never going
to hit 60 Hertz anyway.
So at that point you're back to,
you have to optimize your app.
But if you're finding that
you're optimizing things
and you're using an NSTimer and
you've got it as fast as you can
and you're still dropping
frames, it's probably
because you're using an NSTimer
and just hitting
this variability,
and you should switch to
using a CADisplayLink.
So NSTimer's never
really the right solution
for doing client-side
animations.
So that's the story
for transitioning
from a gesture to an animation.
To give you an idea of now how
to transition from animations
to other animations, Andy's
going to come up on stage
and talk a little
bit about that.
[ Applause ]
>> Thanks, Josh.
So you've got an animation going
and you want to change
directions.
Many of you have probably
seen this issue before.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Many of you have probably
seen this issue before.
You've got this purple circle.
You say, "Go to the other side."
And in the middle of its
animation, you say, "Wait, no,
go back the other way," because
maybe the user pressed a button.
And then what happens is
the circle jumps all the way
to the right side and animates
from there to the left.
This is a pretty common bug
that we see in applications,
and it's one of the reasons
why people will, for instance,
disable user interaction
on their animations,
to get around issues like this.
Now, there's always been
a UIViewAnimation option
that can help deal
with these scenarios.
It's called
UIViewAnimationOption
BeginFromCurrentState.
And this is what it
looks like by contrast.
So there's no jump in position.
And that's great.
But there is a jump in velocity.
If you look at this animation,
it's kind of like
it hits a brick wall
and then it starts going
back the other direction.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and then it starts going
back the other direction.
There's a more fluid option
that I'm going to talk about now
and I'm going to
introduce you to it.
It's called additive animations.
Take a look at how this looks.
The circle on the bottom moves
and it preserves both position
and velocity when it
changes direction.
Both of those are smooth.
Let's see that one more time.
You can see
BeginFromCurrentState,
it's kind of like
it hits a wall.
Additive animations, the
velocity component is smooth.
So absolute animations have
been the default on iOS, so far,
but starting now with iOS 8,
additive animations are actually
going to be the default behavior
for all UIKit created
animations,
[applause] most UIKit
created animations.
We'll talk a little bit
more later about exactly
when this is going to
work and when it's not.
But the long and short of that
is that, if you have this code,
a typical animation
thing, you are going to get
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
a typical animation
thing, you are going to get
that desired behavior.
So, I do want you to actually
understand what's going
on behind the scenes here.
I think that's really important
to taking advantage
of this new feature.
So let's walk through how
Cocoa Touch actually manages
animations in the system.
What's happening behind the
scenes to make this happen?
So you've got this
circle and you tell it,
"Go to the other side."
You say, "Animate that
x position to 500."
So if you were to ask the circle
for its center's x value
immediately after making
that call, it would return 500.
So you think, "Okay, well,
that means the circle
should draw all the way
on the right side,"
but it doesn't.
The first frame that the circle
is rendered is still going to be
at 0 because it's going
to animate from 0 to 500.
And the reason that that
happens is the distinction
between presentation layers and
-- presentation layer's values
and model layer's values.
So if you're unfamiliar
with that distinction,
I suggest watching the
talk that Josh and I gave
in 2011 called Understand
UIKit Rendering.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
in 2011 called Understand
UIKit Rendering.
I'm mostly going to
assume that you understand
that distinction already.
The point is that where the
layer's value is -- I'm sorry.
The layer's current
value is not necessarily
where it's going to draw.
There's a presentation value
that's used for rendering.
So right now the presentation
value of this layer is 0.
And the reason that it's 0 is
that there is this CAAnimation
object which UIKit has created
and attached to the layer.
It encapsulates a few
pieces of information.
You'll note that it
has a fromValue set.
Now, fromValue was read out of
the model value of the layer.
The model value of the layer
was 00, so the fromValue
of this animation is 00.
You'll note that there
is no toValue and that's
because the way this works
is that at every frame,
the toValue is going to
be the current model value
of the layer.
So if we were to
read this animation
in natural text we
would say, "Okay,
this is an animation that's
going to animate from 00
to the current model
value of the layer,
which happens currently
to be 500
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
which happens currently
to be 500
over one second starting
from 1000.1.
So this animation emits a
value of 00 for time 1000.1
and that's why the
presentation value is 00.
The next frame we render, it
emits a slightly greater time.
That is the value that we use
for the presentation layer
and that's how we
move across screen.
Now, if we were to start
reversing direction partway
through this animation,
say we set the center
of the circle back to
0, then just as before,
the model layer's value
jumps immediately back to 0.
But it's not going to
draw at 0 immediately.
So in terms of these
CAAnimations, the next thing
that happens is just going to be
that we destroy the
current CAAnimation.
And that's going to be
why you see that jump.
We create a new CAAnimation
to replace it.
And, just like before, we get
that new CAAnimation's fromValue
from the current
layer's model value,
which was 500 in this case.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
which was 500 in this case.
Now, we didn't make it
all the way to the 500,
but the model value was 500.
So this animation's fromValue is
500, and that means if we were
to read this in natural
text that we're going
to be interpolating from
500 to 0 over a second.
And indeed, the next
frame we render,
the animation emits a value
of 500 and we go ahead and use
that to render the
circle's position.
And that's why we
have that jump.
So we'll animate from 500
back to 0 across the screen
until we finally get to 0.
BeginFromCurrentState
is a little different,
and I'd like you to understand
how that works, as well.
So say that we're at this point.
We were partway through
the animation
and then we emit the code
to reverse direction.
So we say, "Animate again.
This time begin from
current state."
Well, again, we're going
to destroy the animation
that was preexisting and
create a new animation.
But this time, we're going
to create a new animation
with a fromValue derived from
the presentation layer's value,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
with a fromValue derived from
the presentation layer's value,
rather than from the
model layer's value.
Now, at the time this
animation was created,
the presentation value was 150,
and so we make an animation
which is going to
animate from 150
to 0 instead of from 500 to 0.
And, therefore, the next
frame that's rendered,
the animation emits
a value of 150
and so we don't have
that positional jump.
So it seems like
everything's good.
We're animating back
towards the origin.
But we still had that kind of
brick-wall feeling as we looked
at these three circles
earlier, and I'd like you
to understand why
that is now, as well.
So say that we're going to
be animating this circle
from the left side of the screen
to the right side of the screen
and just taking snapshots at
a regular interval as it goes.
The snapshots might look
something like this.
Now, if partway through that
animation we were to stop
and try reversing direction
of the circle again,
and then if we were to take
snapshots at the same interval,
we would get snapshots that
looked a little bit like this.
So the first thing we notice is
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So the first thing we notice is
that we are animating
a smaller distance
over the same amount of time.
And that's going to create
a problem because we can see
that the slopes of these
two lines are not the same.
That's not going to be smooth.
But that's not the
whole problem.
You might think, "Okay, we
could adjust the duration.
We can get around this."
Let's think about easing.
If we're doing ease-in,
ease-out,
this might be what those
snapshots looked like.
And then if we were to
stop partway through
and reverse direction, that
problem would be exacerbated.
Looking at a plot, this
is the situation we have.
Really, the issue we have is
that the velocity
component isn't smooth.
There's that kink in
the middle of the graph.
And that kink is exactly what
additive animations are going
to help us solve.
So I'd like you to understand
how additive animations actually
work under the hood.
Returning to this example
here, let's say we try
to animate across the screen.
As before, we're going to
create a new CAAnimation
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
As before, we're going to
create a new CAAnimation
and we're going to have this
additive flag set to YES.
This is a property that
already exists on CAAnimation.
You can use it in
previous versions of iOS.
It's just that UI is configuring
this for you now by default.
The fromValue is a
little different.
The toValue is now present,
so let's talk about those.
The fromValue and the toValue
are interpreted relatively
to the model value
at every frame
with an additive animation.
That's basically the purpose
of additive animations.
So we create a fromValue by
subtracting the previous value
from the destination value,
basically 2 minus from.
And we create the toValue
by just assigning it to 0.
So what that's going to
do is at every frame,
that animation is going
to emit a contribution --
here, minus 500 -- which,
when added to the model value,
currently 500, will form
the presentation value, 0.
So 500 plus negative 500 is 0
and that's why the
first frame is
that circle all the
way on the left.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that circle all the
way on the left.
And as it starts moving
towards the right,
the animation's contribution
decreases.
It's always getting closer to 0.
So minus 500, minus
450, minus 400,
and the sum is getting
closer to 0.
So now partway through, if
we reverse direction, again,
the model value jumps
over to the left side.
But this time we're not going
to destroy that animation.
We're going to make a second
animation and add it on top.
So now we're going to use both
of those animations' values,
0 plus negative 300
plus 500 is 200.
And you'll see even though
we've added this animation
to reverse direction, we
overshot a little bit.
We didn't reverse
direction instantly,
and that's because we can't
do that without making a jump
in velocity component.
So here we overshot
a little bit,
but now the second
animation's going
to overtake the first
animation all the way
until we get back to the origin.
So they stack.
[ Applause ]
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Applause ]
I'm glad you like it.
So we get back to the origin,
they both have a 0
value, everything's happy.
We had this graph, and
now we have this graph.
[applause] Alright, excellent.
So I promised that I would
tell you a little more detail
about when this is going to work
and when it's not going to work.
So there are a set of keys
on which this behavior
is supported.
Basically they're
your geometric keys.
So this kind of stuff is
all going to work fine
for moving objects around
on the screen, scaling them,
rotating them; that's
all going to be fine.
There are certain kinds
of rotations, though,
which work and ones which don't.
So, just really briefly,
if you were to actually try
to animate the layer's transform
property, you're only going
to get this additive behavior
for affine transformations.
The definition of an
affine transformation is
that it keeps parallel
lines smooth
after the transformation's
applied.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
after the transformation's
applied.
So the bird on the left, we've
rotated it about the z-axis.
Those parallel lines --
I'm sorry, parallel
lines are not smooth.
They're still parallel.
Parallel lines
pre-transformation remain
parallel after the
transformation.
That is an affine transform.
So the bird on the left,
parallel lines still parallel;
bird on the right, parallel
lines no longer parallel.
So a rotation animation
that is like the bird
on the left will use
additive animations;
and rotation animations, like
the bird on the right, will not.
There's a few other
compatibility requirements,
as well.
This feature will not work
with keyframe animations
at the time being and
it also will not work
if there are pre-existing
repeating animations
because that could
get you to a place
where you have an
unlimited number
of animations stacking forever.
It's not good for the system.
Finally, it will also not work
if you've gone behind
UIKit's back
and created preexisting absolute
animations on that layer.
But these are, perhaps,
not common situations.
Now, I said that not
all keys are supported.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, I said that not
all keys are supported.
For instance, alpha
is not supported.
And you don't really know what
setting the circle's tintColor
is going to do.
You know that that's
an animatable property,
but you don't really
know what that means.
And so you can be defensive
about whether properties support
additive animations or not
by continuing to use
BeginFromCurrentState.
So for center.x, for that
animation, we're not going
to use the old
BeginFromCurrentState behavior
behind the scenes.
We are still going to make
that additive animation.
But for the other two
properties, for alpha
and for the tint color, then
at least you'll get position
preserving transitions.
So you can go ahead and
use BeginFromCurrentState,
center.x will be additive,
the others will use
the presentation value
in their reverse directions.
The other thing that's
interesting
about this transition is
sort of a side effect.
You know, UIKit doesn't
actually have a way for you
to cancel in-flight
animations right now.
There's no way to do that.
So some people do it like this.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
They make a new animation
with a 0 duration,
and you just set the
value to something else.
Now, that will sort of stop
in-flight animations just
because the behavior
of UIKitAnimations
has always been that,
when you make a new animation,
we throw out the old one,
replace it with the new one,
and this new one's going
to be 0 seconds long.
But, as we just saw,
we don't throw
out those animations anymore.
So this is not going to stop
any in-flight animations.
Instead you just have to drop
down to the CAAnimation API
directly to do your work.
One other thing to keep in mind
is behavior completion handlers.
These animations stack.
So before, if you
had an animation
with a completion handler, you
started reversing direction.
As I said, we were going
to remove that animation,
so when we remove
that animation,
we call your completion handler.
The argument is finished,
and so when we call
your completion handler,
we call it with a
value of false,
because the animation
didn't finish.
And then we'll create
the new animation.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And then we'll create
the new animation.
And when that one completes,
we'll call that completion
handler.
Well, in the additive
animations world,
if you have an additive
animation
with completion handler and then
you reverse directions, then --
remember, we don't throw
out that animation.
We make a new animation.
And so we don't have
a completion handler
to call right now.
The animation's still going.
It's only when we actually get
to the end of the animation
that we call the
completion handler,
and we call the completion
handler with a finished argument
of true, because that
first animation did finish.
It's just that another
animation was added on top.
So this could be
surprising, potentially,
and what I'm telling you
is, do not be surprised.
[laughter] So, in summary,
transitions will be
smoother by default.
This is a great thing.
However, there are some kinds
of animations that are not
yet supported with this system
and so you should still
use BeginFromCurrentState
if you're not sure about whether
you're animating a supported
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
if you're not sure about whether
you're animating a supported
key, a supported
scenario or not.
That's fine.
We'll do the best
thing we can do.
And, finally, do keep in mind
that those completion
handlers may stack
because that does
actually represent a change
in when we are calling you back.
So we've talked about
how to transition
from animation to animation.
And now that you've got
these animations inflight,
you do still want to be able to
interact with your application.
So Josh is going to come back
up and talk about transitioning
from animations to gestures.
[ Applause ]
>> Alright.
Thanks, Andy.
We're getting close.
We've got one transition
point left
that we're still finding
we've got some issues with.
And by default probably
you're noticing
that in this transition point,
going from your animation back
to your gesture, you're actually
by default not actually able
to even start an interaction.
So before we can even talk
about making it smooth,
we have to talk about how to
allow gestures and touch events
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
we have to talk about how to
allow gestures and touch events
and interactions to begin
while you're in the middle
of one of these animations.
So let's go and take a look
at why this is the case
and what we can do about it.
So here we've got some big
view and I've put a subview
in that I've colored blue
just to make it stand out.
And we're going to say
that we're taking that view
and we're animating it over to
the right into this position
on the right side of the screen
with some standard
UIKitAnimation using the
UIViewAnimateWithDuration API.
And let's say it's
halfway through.
And halfway through, we want
to start interacting with it.
So let's say that a touch comes
down on the screen and it comes
down on top of where
that is visually
in the presentation layer.
Now the default thing
that you get
if you just do
UIViewAnimateWithDuration is
that animations that are
in progress as a result
of that do not allow
user interaction during
the animation.
So that view is going
to get hit tested
and the touch will be
attached to that view,
but because the interaction
is disabled by default,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
but because the interaction
is disabled by default,
what you'll find is that the
touch actually doesn't get
delivered anywhere and
just gets swallowed.
It just disappears.
It's as if it didn't
even happen.
So obviously we want to fix
that, and the way that we can go
about doing it is by using an
animation option called UIView
animation option called
UIViewAnimationOption
AllowsUserInteraction.
Not too surprising, the name.
And you can pass this
into the UIViewAnimation
AnimateWithDuration APIs.
And this will enable interaction
during these animations.
Now, once you've opted in to
allowing user interaction,
you're basically
saying, "I've got this.
I'm an advanced developer
here and I know what I'm doing
and I'm going to take
care of making sure
that the right thing happens."
So, you know, by default,
UIKit is making it safe
so that you don't have
weird things going on.
But once you opt in, you're
saying that you're going to deal
with it and you know how
to do the right thing.
So let's talk about
what the right thing is.
So, again, let's
look at what happens
if a touch comes
down in that view.
Now, this part is probably
going to be a little surprising
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, this part is probably
going to be a little surprising
at first if you've tried it.
What actually happens
is that you'll find
that the touch gets hit tested
to that big view in the back,
which probably wasn't
what we meant
because we touch visually
on what was there.
In fact, if you go
and put a touch
down where we're moving
the additive view to,
where it's going to animate
to, you'll find the thing
that we hit test is the thing
that is currently
animating over to there.
Understanding why
that happens goes back
to what Andy was saying about
the difference between model
and presentation values.
Once you've enabled user
interaction, you're saying
that you're going to take
control of this whole system
and you're going to decide the
right place to go do a hit test.
Because we don't
actually know if the thing
that you're animating is
something that you intend
to interact with or just
something that is animating
in a system, but you're
actually trying to interact
with the thing behind it.
So really we're hit
testing the model values
of all these layers, but
if you want to interact
with the thing that's
animating, you really want
to be hit testing the
presentation values.
So we have to go talk about how
we can make a presentation layer
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So we have to go talk about how
we can make a presentation layer
hit test, instead of a
default model value hit test.
And to do that we can override
the hitTest withEvent method
on our UIView that's animating
to make it so that it's going
to hit test its presentation
value instead
of its model value.
Now, by default, the method
is going to take a point.
It's the point in the view
that is being hit tested
and that's the view,
or the point
that UIKit thinks
you have touched.
But, as I mentioned, it's
done that in model space.
So we have to convert it back to
presentation layer space to see
if it's currently inside where
we are as we're animating.
So we can take that
point that's passed in
and convert it back
into our superview.
So we'll convertPoint
toView or superview.
Once we've got it into
superview's coordinate space,
then we can convert it back into
our presentation layer space.
So we convert out using
model space and then back
in using presentation
space so that we can see
where it is inside of
where we are currently,
given that we have an
animation in progress.
So we can do that by getting
our presentation layer and using
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So we can do that by getting
our presentation layer and using
that to do the conversion back
into our coordinate space.
Once we've got that converted
into the right space,
then we can just call super
and let it do what
it was going to do.
So return super.hitTest, passing
in the converted point now
in presentation space
and everything else can just
move on as it had before.
So once we've done that,
we can go take a look
at what was going on again.
Now we've enabled these
interactions, we touch down,
and now the right view is going
to get hit giving what we
were trying to accomplish.
Now, keep in mind
that other APIs --
like touch, location and view --
are also assuming model
space conversions.
All the UIKit behaviors
by default are converting
using model space.
So if you've now
enabled hit testing
into something that's animating
and you're in the progress
of doing that animation and you
want to start handling touches
in the view, if you ask for
that touch location in view,
it's going to be
back in model space.
So you would have to be
careful to go and make sure
that you know which space
you're interacting in.
So really, the reason that the
default is disabled is because,
once you enable interactions,
you have to start knowing more
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
once you enable interactions,
you have to start knowing more
about what's going
on and being careful
to be using the correct
coordinate spaces.
But most of the time, this is
actually probably not going
to be a problem, because
usually if you're enabling
under interaction on
something that's animating,
you're probably trying
to grab the thing
and stop the animation.
So really what we're
going to talk
about now is how we
stop the animation.
That's the key part that
we're interested in right now.
We've got hit testing working.
Once we're touching down,
how do we stop an animation?
Now, as Andy mentioned,
there is no direct API
on UIKit to stop an animation.
So we're going to use the
CALayer API to do that.
But there's another interesting
component that we have to keep
in mind, because Andy just ran
you through the whole story
of how things go when there
are animations on a layer.
And part of that is that
the model value is already
at its final location.
So if we just removed the
animation from the layer,
that was the thing that was
keeping it in the center
of the screen visually
right now,
so it would snap to the end.
So we would touch down on the
object, remove the animation,
and it would snap out
from under our finger
and land wherever it was going.
So we don't want that.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So we don't want that.
We have to go get the
presentation values
of where it currently is
on screen and set those
to be the new model values
so that there's no jump.
Now let's assume the thing we
were doing was animating just
its position, its
location on screen.
I'll give you an example of how
that would look in that case.
Of course, if you're animating
something else, like its bounds
or some other property, you'd
have to do the same thing
for whatever property
you're animating.
So in this case we're
just doing the center,
so let's get the
view, get its layer,
get the layer's presentation
layer, which is going to be
where it is on screen right
now; and in this case,
I'm asking for its position
because that's where --
its location on screen.
So we'll store that away here.
Then we just want to set that
to be the new model value
so that once we remove the
animation it doesn't jump.
It's going to end up right here.
Now, presentation - I'm
sorry, UIView's property
for this is called center.
CALayer's is called position.
They're actually the same thing,
so you can just assign one
to the other and not
really worry about it.
They are the same property.
They just have a different name
in UIKit and Core Animation.
So once we've set that, now
the model value is putting it
where we thought it
would be visually,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
where we thought it
would be visually,
because that's the
presentation value.
So we can just go and
remove the animation.
Now, in this case I'm
taking the shortcut
and just removing
all animations.
Now, you probably want to be a
little more careful than that
when you're doing it yourself
to make sure you're removing
only the animations you really
mean to be removing.
You have to be cognizant
of whether
or not there are others there.
But that's the key to it,
is just removing the
right animations.
So I mentioned that things were
going to be a little easier
when we were talking
about UIDynamicAnimator
earlier in the talk.
Now, this is the place
where things get easier
when you're using
a dynamic animator.
Because DynamicAnimator
doesn't have presentation
and model space, we don't have
this same complication of having
to figure out where it
is on-screen compared
to where the model is
or anything like that.
DynamicAnimator, it's
all just wherever it is
and it's happening
in your process
and the model value is
the correct position
on screen right now.
So stopping this
for a DynamicAnimator
is a lot simpler.
So let's take a look at that
code that we wrote earlier
in our GestureRecognizer method
where we were handling
the ended case.
Now, of course, we're trying
to deal with new touches coming
down so we want to
handle the began case
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
down so we want to
handle the began case
on our Gesture Recognizer.
So we'll make some
space for that.
And my suggestion for
what you would do here --
there's a couple of things you
could do, but the one I'm going
to suggest as a good option --
is to actually just remove
the thing that was animating
from the dynamic item behavior,
because once you've removed it,
it's no longer going to interact
with any of the other views;
it's going to lose any
velocity it may have had,
because you just took it
out of the dynamic system;
and now you'll be
able to manipulate it
and move it around yourself.
So we can take the dynamic item
behavior and remove the thing
that we're trying to interact
with, our target view.
Now, of course, once we've
removed it, it's no longer going
to have any new velocity when
we try to add velocity later,
so we have to put it back
in when the gesture's done.
So we'll take that
dynamicItemBehavior
and add it back at the end.
And there's other things
you could do instead.
If you really wanted to leave
it in the dynamic system,
you could subtract off
the current velocity
so it would just come to a rest.
But, first of all, the easiest
thing to do is to remove it,
and it's also probably what
you mean in a lot of cases,
because it'll stop it from
smacking against other views
in your system as
you drag it around.
So now we've really dealt
with most of the transitions
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So now we've really dealt
with most of the transitions
that we wanted to talk about.
We got our gesture to animation
going, we got animation
to animation going,
we got animation back
to gestures working.
So it seems like
everything's good.
We all -- visually,
everything looks great.
So we should be done, but
we've got 15 minutes left,
so what's going on?
Well, there's some complications
that really start to come
into things once you do
this in a real application.
So far everything
we've been talking
about is pretty theoretical.
If you're just doing animations,
it's all pretty easy.
But if you -- in any real
application you have other
state, transient
animation state,
that exists only during
transitions or animations.
Maybe when you start some
transition, you create a bunch
of extra views to use
during that animation,
and then when the animation
is done, you destroy them.
You have to know what the
right times are to create
and destroy these things.
And if we're stacking animations
with additive animations
and we're figuring out
that we're starting
and removing things in the
middle by allowing interaction
with gestures, it's no
longer immediately obvious
where the right places are
to make some of these changes
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
where the right places are
to make some of these changes
to your view hierarchy
to set up and tear
down your transient
animation state.
So we're not going to
go through too much
in slides about this actually.
What we're going to do is just
do a demo showing you how you
can make this happen
in a real app.
So Andy's going to come back
up and write an app for you
that does all of that.
>> Thank you very much.
I am extremely excited
about this part of the talk.
I don't think we've ever talked
about this topic publicly
and we've been confronting it a
great deal ourselves as we think
about how to effectively deal
with these kinds of problems.
We want everything to be
fluid as much as possible
in our system, and that means
wrestling with these problems.
There's a superstructure of
state around your animations
which you need to deal with,
which you can't forget.
So I have a very
important application here.
It's been a key thing for us.
We're using it as a test ground
for this experimentation,
and it's called Toggle Bird.
Now, sometimes you need a bird.
So, great features, you press
a button and get a bird.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So, great features, you press
a button and get a bird.
Sometimes you don't want a bird.
You press a button and
the bird goes away.
Now you don't have
a bird anymore.
That's great.
But, sometimes you're
indecisive.
You say, "Okay, well,
does this bird --
no, I don't actually
want the bird.
I was overeager with the bird.
Put the bird away.
Go away, bird."
But you can't, because
the button's disabled.
And that's frustrating.
You don't really want the
feeling where an animation's
in progress and so
the world must stop
until the animation is done.
So we could look at our code
and say, "Oh, okay, well,
I see a couple of pretty
big red flags here.
Looks like we're disabling
and re-enabling the birdToggle
button when this animation's
in flight, so I'll just go
ahead and comment out that code
and then everything
will be cool.
You know, the button's not
disabled anymore, looks good."
Let's walk through
this method together
because I think it
actually illustrates a lot
of the common problems that
people have when trying
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of the common problems that
people have when trying
to make this kind of thing work.
So this is just the setter
for the birdExpanded property,
which you'll see
is just a property.
And this is a property
which is animatable,
which means I've
designed it so that,
if you set this property
inside an animation block,
then it'll be animated.
I think that's a nice pattern.
We do that by inheriting the
parent's animation context
by making a UIView
animation block
that is a 0-length duration.
So you'll see here
there's a toggleBird method
that sets the birdExpanded
property inside an
animation block.
That's how that part works.
So let's walk through
the rest of it together.
There's a bit of state
that we keep track of.
Is the bird expanded?
And if the bird is supposed
to be expanded anew,
we make a birdDrawerView,
set it up.
We want it to animate from,
you know, being full-width
but 0 height, so we do a
little layout initially.
So this is kind of
like a preamble.
You know, we're making
some stuff that we need,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
get it set up, and then we
actually do the animation.
This is the part that is now
additive, so that's great.
You know, we're just changing
this constraint's constant,
telling it to layout inside
of an animation block.
That will synthesize the correct
bounds and position animations
to actually get things moving.
And finally there's
something of a post-amble
when that animation's completed.
If the bird is no longer
supposed to be expanded,
we clean this up because
the bird is being drawn
with some very novel
and expensive rendering
technologies, and we don't want
to waste system resources
on that.
You know, you've got
to be good Samaritans.
So this is the entirety
of what's going on here,
and the problem arises when we
remove these enabled setters
because we can run through this
method multiple times while the
animation's in flight.
This completion block, as I said
earlier, is going to run even
if there are new
animations related
to the bird also running.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to the bird also running.
So we really have a more
complex state machine going
on than it seems at first.
So I'm just going to jump
right to the solution here.
Only a few lines change.
And first let me prove
that I have solved our
bird toggling problems.
It's very nice.
We can be indecisive
about the bird safely.
And here in the code I've
only changed a few lines.
I've got a new ibar,
and that ibar is going
to be tracking how many
active transitions we have.
So at the bottom, every time the
birdExpanded property changes,
we increment that; and
every time a bird expansion
or contraction animation
completes, we decrement it.
So we're just keeping
track of the number
of in-flight state
transitions currently.
And then we change our
preamble and post-amble
to consider that counter.
We say, "Okay, well,
only do this preamble
if we're transitioning from
not having any animations
to having some animations.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to having some animations.
Similarly, only do
our post-amble
if we're transitioning
from having animations
to no longer having
any animations at all."
And that alone solves
this problem.
I do want to show you
what this would look
like with iOS 7-style
animations.
Note that the bird jumps to
the finished value when I try
to make it reverse direction.
So even if I were to use the
BeginFromCurrentState option
to try to use presentation
values.
You see that when I
reverse direction,
it's kind of like the
bird hits a brick wall.
Now, we much prefer the
additive animation solution.
It's much smoother.
So all that's great, and I
want to show you quickly,
I'm just going to comment
this a little more in order
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
I'm just going to comment
this a little more in order
to make clear how general this
structure I've created is.
Almost everything I've
written here doesn't have to do
with birds or toggling.
That is the key point,
because you might be writing a
frog toggler.
You know, I don't know; I
want you to be creative.
So there's a few sections here.
There's a preamble section.
In the preamble section,
you can deal with all
of the possible states.
We only have a preamble for
the bird becoming expanded.
You could have a
preamble the other way.
Similarly, this counter, that's
not about birds or toggling.
That's just about
transitioning between states.
And in the post-amble, we
don't have a post-amble
for the bird being expanded.
We could. We have a post-amble
for the bird being contracted.
I want to point out that we're
checking the state at the time
of the completion handler
being called rather
than the argument being passed
into this method because, again,
these completion
handlers can stack
and the state might no longer
be what you thought it was.
The one last thing I
want to point out is
that this is the
solution for two states,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
but if you have three states
that you could move between,
you could handle
that this way, too.
You just need a counter
for each of the states,
and that counter would track
how many transitions to or from
that state or in-flight.
Right now we only have one
counter, because the number
of transitions to or
from each of the states
in our bi-stable system
is always the same.
So this is a solution
which generalizes
and which doesn't have anything
to do with birds or toggling.
>> Alright, let's head
back to slides to wrap up.
>> Thank you.
[ Applause ]
As I said earlier, we don't want
to stop the world while
animations are in-flight.
They might be beautiful,
but your user is
trying to do things.
We also want to keep
things fluid.
So, use that gesture
velocity and translate it
into the animated actions which
result and makes an interface
which feels more responsive.
Once you've got animations
in flight,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Once you've got animations
in flight,
make sure that they smoothly
transition to any new animations
which might replace those
first sets of animations.
Finally, as I was
saying a moment ago,
don't stop the world.
Try to make animations
interruptible
as much as possible.
It is more work,
but we've talked
through some techniques
for dealing with it.
And, finally, keep in mind
that there may be
additional associated state
on top of these animations.
That is often what
makes it more difficult
to enable user interaction
while animations are in flight.
But we've just talked
through one technique
which I think is quite
general for dealing with that.
And finally, if you
have more questions,
I encourage you to ask Jake.
We have a Core Animation
Programming Guide
which actually does talk
about many of these topics
and the developer forum
where you can ask
your peers questions.
I hope you've had a
fantastic conference
and I can't wait to
see what you make.
Thank you.
[ Applause ]