WWDC2016 Session 216

Transcript

[ Music ]
[ Applause ]
>> Good afternoon.
My name is Bruce Nilo.
I'm an Engineering
Manager for UIKit.
And today, my coworker, Mike
Turner, and I are really excited
to talk to you about
some new animation APIs
that we're introducing
to UIKit in iOS 10.
These APIs we're confident are
going to make it even easier
for you to create natural,
and smooth-feeling,
and responsive applications.
So we're going to begin our talk
today with a quick refresher
of the existing implicit
animation APIs
that exist in UIKit.
We're then going to dive
into a brand new class
that we are introducing
called UIViewPropertyAnimator.
We're going to talk about
some extensions we've made
to view controller transitioning
so that you can take
advantage of this new class.
Mike is going to come
up and give a great demo
that uses these new APIs
and a brand new sample Photos
application that you'll be able
to download in the near future.
We're then going to talk about
a few subtle issues related
to touch processing and hit
testing for this new class
and how you can use
this new class
to create even interruptible
keyframe animations.
So, implicit property animations
in UIKit basically are there
to -- well, they exist
in a way that allows you
to -- well, they exist
in a way that allows you
to create a dynamic context
by which you can update
the properties of a view
that can be animated over time.
It's an implicit API because
UIKit creates the animation
on your behalf.
It notes when the value changes
and uses the original value
and the end value to interpolate
the value over time based
on the duration that you specify
with an optional timing function
that specifies the
pacing of the animation.
So let's make this
a little concrete.
Here's a very simplified
animation.
We're going to use this
throughout the presentation.
We have a circle
that's basically going
from the left to the right.
It's the center property of
that view that's animating.
And when in this graph
we see a dotted circle
or a dotted square, it
means that's the value
that you're actually
seeing animate.
Whereas the solid shape is going
to indicate the target value.
And we sometimes call
that the model value
versus the presentation value.
So we start the animation,
and it proceeds a pace.
In fact, you'll notice that
the pacing was uniform.
And the question is -
how do we do that today?
Well, today it's really easy.
Most of you are probably
familiar
with the animateWithDuration
API.
And in this case, we're
specifying that the x coordinate
of the center of that circle
should move from 0 to 100.
Now the timing function that
was specified there was .linear,
which basically means it
was the identity function.
Now a quick review of what a
timing function is in UIKit.
It's basically a cubic function
that maps "0 to 1" to "0 to 1"
with a couple of constraints.
Namely, that at the
start of the animation --
meaning when you just started --
the progress of the value
that you're animating is
the progress of the value
that you're animating is
in fact the start value.
And at the end, it's
the end value.
Now timing functions that
are not identity pace
your animation.
And it's really easy to see this
with this exaggerated
ease-in timing function.
You'll notice that, halfway
through the animation,
the progress has barely moved.
It's really chugging
along pretty slowly.
We're almost 90%
of the way done.
We're still only halfway there.
It's in the last 10% of
the time that you specified
that we're going to make up
the rest of the distance.
Basically, this thing
is accelerating
from a very slow pace to a
high pace towards the end.
So that's what timing
functions give you.
UIKit currently really only
gives you four timing functions.
These are specific instances
of cubic Bézier functions.
There's the identity
function we saw before.
There's the identity
function we saw before.
There's .easeInOut.
There is .easeIn.
And finally, there's .easeOut.
And these are very subtle
functions that we give you
to kind of affect the
feel of your animations.
Now what about springs?
We also have an implicit
animation API
where you can specify a spring.
And you wouldn't be
wrong if you said, "Well,
that's not really interpolating.
We're kind of overshooting
the value
and kind of bouncing back."
However, in what follows,
I'm going to encourage us all
to think about the spring as
another type of timing function.
And let's see why
we can do that.
Let's look at this
simple application
where we're animating a
square from left to right.
And we're graphing effectively
the position of the square
And we're graphing effectively
the position of the square
as it proceeds over time.
And it kind of looks
like a timing function.
The thing is it's not a cubic.
It overshoots its values,
but we can still think of it
as affecting the
pacing of the animation.
And in the APIs that follow,
that's how we are going
to classify spring animations.
Now there is another
important edition that was made
to animations in iOS 8.
And I want to talk to you about
them because we're going to look
at them again a little
bit later.
Basically, consider an
animation that changes
in the midst of the animation.
So you're going from one
position to the other.
And halfway through,
you change it.
Now prior to iOS
8, if you did that,
there would be a discontinuity
in the animation unless you
specified a special option --
UIViewAnimationOption
BeginFromCurrentState.
And if you did that, it would
look a little bit better.
There wouldn't be a
discontinuity or a jump,
but it wouldn't be
smooth either.
The velocity changes
rather abruptly.
The velocity changes
rather abruptly.
In iOS 8, we made a significant
change where animations
for certain properties
were done additively.
We didn't remove the previous
animation, we added to it.
And then that smoothed out
the change in velocity.
With the new
UIViewPropertyAnimator,
it's going to be impossible to
get into the first situation.
OK, let's get to
the new class now
that we have done
the quick refresh.
Some of its features.
It's really familiar.
If you're familiar with the
existing animateWithDuration
suite of APIs, you'll be right
at home with this new API.
It's interruptible.
That means you can pause
and stop the animations.
You can scrub them, meaning you
can move forward-to-back as you
like based on a programmatic
API.
You can reverse them.
You can say, "Never mind.
Go backwards."
We're going to introduce
a whole new plethora
We're going to introduce
a whole new plethora
of timing functions, not just
the four that we had before
with one kind of small
subset of spring animations.
And finally, when the
animations are running,
it'll be really easy to add
to the running animations.
So it's kind of hard to talk
about API, and I don't want
to do it by going into
kind of a header crawl.
So let's get an overview of
what this API looks like.
At the center of it all
is this new class --
UIViewPropertyAnimator.
It's implemented in terms
of two new protocols.
And the reason for implementing
them as protocols or conforming
to two new protocols we'll
see is really powerful
when we couple it with view
controller transitions.
When you create a property
animator, you're going
to create a new type of object
which is effectively
specifying the timing function
that you want that
animation to use.
We provide two new
concrete classes for these.
We provide two new
concrete classes for these.
We've introduced a couple of
new enumerations that are used
by the methods defined
in these protocols.
For example, the state
of an animator can be
inactive, active, or stopped.
And we're going to be talking
about that momentarily.
We also introduced a
position, which basically is
where did the animation finish.
Did it go all the way to the
target values that we specified?
Did we reverse it and go
back to the beginning?
Or maybe we interrupted it
and we're somewhere else.
OK. We're going to
start focusing
on the center part
of this graph.
We're then going to
talk a little bit
about all these new
timing functions.
So let's look at kind of
the corner piece of the API
which is defined by the
UIViewAnimating protocol.
This is where all the fun is.
It's really simple actually.
There's a couple of properties
that you can observe and set.
For example, you can set
the fractionComplete.
You can tell an animation
to start.
You can pause it,
stop it, finish it.
It's kind of self-explanatory.
The next protocol adds the
implicit characteristics
to this animator.
It's where you add blocks
where you set the target values
of the properties that
you wish to animate.
There's also an interesting
method called continueAnimation
which allows you to proceed
from a paused animation
with a completely
different finish duration,
and possibly even a
different timing function.
UIViewPropertyAnimator has
a couple of constructers.
It also has some properties
that control the touch handling
that we're going to get into
at the end of this talk.
So we're going to talk first
about how to use this -- basics.
We're going to talk about some
details that perhaps are not
so obvious that you might
encounter when you pause
and scrub an animation.
There's also some interesting
possibilities in terms
of how you can reverse
an animation.
of how you can reverse
an animation.
Finally, we're going
to introduce all the
timing providers.
So let's start a
really simple animation.
And I've made this one a
little bit more interesting
for pedagogical reasons
that you'll see soon.
And let's ask the question,
"How would you use the
property animator to do this?"
And it's really, really easy.
The first thing you do is
you create a timing object.
This is just like the .easeInOut
enumerations you've used
in the existing API.
You instantiate one of these
objects by specifying a duration
and the timing object.
You add animations
in terms of closures,
which are updating properties
that you want to animate.
You can add an optional
completion.
In this case, that completion
is changing the color back
to orange.
And then you can start
it whenever you want to.
OK, it's kind of verbose
compared to the old API,
OK, it's kind of verbose
compared to the old API,
but now you have an
object and there's all kind
of things you can do with it.
It makes a world of difference.
So let's look at some of the
properties that are available
in UIViewPropertyAnimator.
There's three that
are quite interesting.
There's the state, there's
whether it's running or not,
and whether it is running in the
forward or reverse direction.
These are observable properties
in the KVO sense of the word.
Now the first thing you do
is you add an animation.
We're going to animate the
circle again from left to right.
Once you do and you've
added an animation closure,
you can start the animation.
The second you start
the animation,
the state changes,
it starts running.
The values of the
actual views change now.
There is target value
and there's what you
see on the screen.
And then the animation starts.
Now what's different
now is I can pause
that animation halfway through.
And to do that, I just
tell the animator to pause.
I can tell it that I
want it to continue again
I can tell it that I
want it to continue again
in the opposite direction.
I'm still paused when
I do, and you'll notice
that the states change.
And then I can start
it, and it's going
to start going backwards.
And then I can change it again,
and it's going to go forwards.
And when it completes,
we're going to call
any completion handlers
that you've registered.
And in this time, we're
calling it with a .end position
because we made it all the
way to the initial target.
If we hadn't reversed it
again while it was running,
we would have called the
completion with a .start.
That way, your completion
handler knows
where the animation
actually ended.
OK. Instead of pausing
an animation,
there's something
really neat you can do.
You can stop the animation.
Now what does that mean?
Well, the first thing you'll
notice is that, all of a sudden,
that circle became
solid in the center.
We've effectively promoted what
you're seeing on the screen
to the actual model value that's
in the view that's animating.
to the actual model value that's
in the view that's animating.
At that point, you can
do anything you want
with that circle.
But we passed in a
false parameter to stop,
which means don't
jump immediately
to the inactive states, stay
in this quasi-finish state.
And what that means is that,
at some point in the future,
you're going to call
finishAnimation and you're going
to specify a position where the
animation is in fact finishing.
In this case, we're
not finishing neither
at the end or at the beginning.
And you're completion
handlers will be called
with that position.
Now if you called stopAnimation
with a true parameter,
we wouldn't even call
your completion handler.
It's just finished
right then and there.
Now let's look at what happens
if you call
finishWithADifferentPosition.
Let's say we call
finishWithTheEnd.
It immediately jumps to
where the animation was
originally targeted.
Now you might think, "Why
would I want to do that?"
And it actually gives you
a lot of possibilities.
For example, imagine
you're pausing this circle
For example, imagine
you're pausing this circle
or you're stopping this
circle when you touch it,
and then you're dragging
the circle around.
And maybe you set up
some UIKit snap behaviors
in the end position and
the target position.
And when you release it, based
on the velocity of your gesture,
it may snap into those
relative positions.
And once it reaches that, you
can then call finishAnimation
with a .end or .begin.
And so you can do some
interesting things
to finalize the animations
that you've started.
[ Applause ]
OK, pausing and scrubbing.
Not much there, right?
Well, it's kind of interesting.
So there's a sample app here
which we are going to --
where we set some gesture
recognizers on the square and on
that little green progress bar.
And we can scrub
that progress bar.
And we can scrub
that progress bar.
We're updating where
the square is.
And then we can continue
the square.
And you'll notice that kind
of the position over time --
there's kind of a weird
protuberance in that graph,
which we're going
to talk to in a bit.
Before we do that, let's look
at what these gesture
recognizers look like.
On the square, we added
a tap gesture recognizer
which checks whether or not
the animation is active.
And if it is, it checks is
it running or not running.
And if it's running,
it's going to pause it.
And if it's not, it's
going to start it up again.
Pretty straightforward.
On the progress bar, we set
up a pan gesture recognizer.
And we're going to just compute
a fraction based on the position
where we are in the pan relative
to the full bounds
of that progress bar.
And based on that fraction
that we've computed,
we're going to update
both the animator
and the progress bar
as fractionComplete.
OK. So, what's going
on with this graph?
There's actually something
interesting going on here.
[ Laughter ]
And to explain what, I'm
going to go back to this kind
of exaggerated ease-in
curve again.
So let's say this time,
50% of the way in,
we pause the animation and
now we want to scrub it.
The important thing to note
is that the fractionComplete
that you're actually setting
has nothing to do with time.
We've just paused the animation.
What you're really
setting is the fraction
of progress towards
the end value.
And in this case, we've hardly
made any progress at all.
Now we're going to scrub
that line and we're going
to want to continue it again.
We don't want to jump
the perceived position
of the view that
we're animating.
So, in fact, what happens
is we kind of map time back
So, in fact, what happens
is we kind of map time back
to the timing function of
the original animation.
Which means when we continue
in this particular case,
the animation is going
to finish really quickly.
So when we go back to the curve
that we actually saw before,
you'll notice that we're
kind of jumping back
onto the timing function
-- that easeInOut function.
And it's important to note
that when you're pausing
and scrubbing, depending
on whatever UI affordance
you may be using to do this,
you may see these
discontinuities and have to kind
of compensate for them.
OK, let's talk a little
bit about reversing.
There's three ways
you can reverse.
One is you can pause reverse --
kind of interact with something
and start it up again.
And you'll notice that you're
literally reversing right back
through the timing function
that you've specified.
You can also reverse on the fly.
Which means that, as the
animation is running,
Which means that, as the
animation is running,
you can tell it to reverse.
And that's kind of like
hitting a brick wall.
Again, there's going to be this
huge discontinuity in velocity.
And that might be what you
want if you want to kind
of represent, say, a
perfectly-inelastic collision
or something like that.
But if you don't want that and
you want it to be smoother,
you can animate additively.
And in this case, we will
reverse the animation not
by changing the reverse
property, but we're going
to actually change the values
back to the original values.
Now what's different
about this is
that when your completion
handlers are called,
they're going to be called
with a position of .end.
You've basically
changed the target.
You're no longer going
towards the original target.
You're going back towards the
target you just specified.
OK, there's some
interesting timing objects
that we've made available
to you.
that we've made available
to you.
The first is the
UICubicTimingParameter class.
If you create this class with
no parameters, you're going
to get the default core
animation timing curve.
This was previously
unavailable at the UIKit level.
The second variant
of the constructor is
essentially the existing canned
animation curves.
And finally, we opened up all
of the cubic Bézier
curves available
on the unit square to you.
And as an example of
something that you might do --
this particular Bézier curve,
if I were to give it a name,
it would be "speed
in, speed out".
And you can kind of go wild
with the Bézier curves you want
to create for timing functions.
[ Applause ]
We've given you more ways
to animate like a spring.
And, again, consider that
we consider springs now
to be influencing the timing.
If you create a
UISpringTimingParameter object
with no arguments, you're going
to get a critically-damped
spring animation,
which many of you
have asked for.
It's one that we use, for
example, when we push or pop
onto a navigation controller.
[ Applause ]
The second variant
is very similar
to the existing UIKit
spring animation API
with a small difference that
I'm going to get to in a second.
But before we get to
that, we've opened
up basically the
spring equation to you.
You can specify any
coefficients you want,
and we will effectively
honor those.
There's a point here though, and
that is that the duration now
that you specify is ignored.
And we're computing that
duration based on the solution
of the spring equation.
You're going to see
an example of that.
[ Applause ]
I said there was a difference.
You'll notice that the initial
velocity is now a vector.
It's not a scaler.
Most of the time, we're
actually not going to look
at the y component
of this vector.
However, if you are animating
the center of a view,
we're actually going
to look at both the x
and y components of that vector.
And to see why, look at
this example application
where we drag a square
off the center.
And when we release it,
we're going to spring back
to the center based on the
velocity of the gesture.
Now, up until now, the velocity
was always along the line
connecting those two squares,
which was kind of unfortunate
because my gesture might be
released anywhere along the
plane, and we effectively
really weren't taking
that into account.
However, now we are.
So let's look at
this little video
which shows how we now will
spring back to the center
and take into account the
two-dimensional vector position.
You'll notice that,
when we go off,
we're going to take both
the x and y components.
It's really easy to do that.
[ Applause ]
OK, we have this great
property animator.
And guess what?
I want to take advantage of it
for custom view controller
transitions.
I want them to be
interruptible, too.
Now, three years ago or so,
I gave a whole talk on this,
and so it's a little
bit complicated.
So if you're not familiar
with custom view
controller transitions,
please refer to that talk.
But I'm going to give a quick
run-through to set the stage
of how we've extended
these protocols.
View controller transitions
basically are a bunch
of interlocking protocols --
two of which interactive
transitioning
and animated transitioning
are protocols
that you create objects
which you conform to --
well, the objects that
you create conform to.
The system will create
another object that gets passed
into the methods
of that protocol.
And this will become
clear in a second.
But let's remember why
we want to do this.
But let's remember why
we want to do this.
Imagine you have an app
with a navigation controller
and the pop kind of looks OK.
It's that great,
critically-damped
spring animation.
But, you know, your app
wants something else.
Your app wants to really kind
of have a different look --
something that explodes
out maybe
with a blur effect underneath.
That's what custom view
controller transitions let
you do.
And moreover, it
lets you do those
and drive those interactively.
OK. The way we get the
objects that conform
to these protocols are
just via a delegate.
It might be the navigation
controller delegate,
or it might be a
view controller's
transitioning delegate.
And we're going to ask that
delegate, "Do you have an object
that conforms to animated
transitioning whenever you do a
present, or a push, or a pop?
And if you do, we're going to
bypass the built-in transition.
We're going to create one
of these context
transitioning objects
which give you all the
information you need
to actually animate
a transition.
to actually animate
a transition.
And we're going to
call animateTransition,
passing in that context to you."
Now how do we make
it interruptible?
Well, what we've done is we've
added a new optional method
called
interruptibleAnimator(using.
And if you implement that,
you're going to return an object
that conforms to
UIViewImplicitlyAnimating.
Now this could be a
UIViewPropertyAnimator,
but it doesn't need to be.
It could be, for example,
another type of animator that,
say, you implement in
terms of UIKit dynamics
or some other animation
strategy.
If you do not implement
the interaction controller,
meaning you only implement a
custom animation controller,
then you need to
implement animateTransition.
And you would do so very
simply, like this method.
You take the interruptible
animator that you would return
and you would basically
tell it to start.
And that's really
all you need to do
to implement animateTransition.
However, we kind of advise
that you use an interaction
controller if you're going
to make it interruptible.
And, again, the first thing
you have to do is conform to
or return an object
that conforms
to animated transitioning.
We will then ask you for
an object that conforms
to interactive transitioning.
And we're going to pass the
object that you return back
to us previously as a parameter.
Often times, you want that
interaction controller
to actually drive the
animation controller.
If you do, we're not going
to call animateTransition.
We're going to call
startInteractiveTransition
and we're going to pass
in the same context
that we would have passed
into animateTransition.
We provide a concrete
class that you can use
that makes this really easy.
Now there was a restriction
before
where if you implemented
UIPercentDriven
InteractiveTransition,
you actually needed the
animateTransition method
of the animation controller to
have been implemented in terms
of the existing UIKit
animation APIs.
But now with the interruptible
animator, UIPercentDriven
InteractiveTransition actually
doesn't care about that.
It's just using the
protocol that we've defined
in UIViewImplicitlyAnimating.
And that's it.
So you could actually have a
completely different animation
that's driven by UIPercentDriven
InteractiveTransition,
which is kind of cool.
OK, let's say you already
have one of these things,
you already have a
custom transition.
How do you migrate?
Well, one way to do it would
literally be rename your
animateTransition method
to myAnimateTransition
and build your interruptible
animator in such a way
that you just add that
method call into one
of the animations
of the animator.
Not too difficult.
We did have to extend some other
objects in these protocols.
We did have to extend some other
objects in these protocols.
For example, context
transitioning now has
a pauseInteractiveTransition.
That's how you enter into
the interactive state now.
Prior to this, you
started interactive
and you ended non-interactive.
Now, you can move
back and forth.
And the way you do that is
by calling pauseInteractive,
or finish, or cancel.
Also, the property
values isInteractive
and transitionWasCancelled can
now change as you move back
and forth between an interactive
and non-interactive transition.
We added a variable called
wantsInteractiveStart.
Because now that you
can move and forth,
maybe you want your
interaction controller to start
out non-interactively.
And this variable
controls that behavior.
Finally, we have UIPercentDriven
InteractiveTransition updated.
There are a few rules.
interruptibleAnimator
-- if you implement it,
we expect it to actually
be there.
Meaning don't do something else.
The system might get grumpy.
We're always going to
call animateTransition
and startInteractiveTransition
first.
So that would be a great place
to create your interruptible
animator because we are going
to be calling it subsequently
with the same context,
and we expect the same
instance of animator
to always be returned.
Finally, the animator survives
the life of the transition.
It shouldn't become inactive
until the entire
transition is over.
Now, Mike is going to
come up and show you how
to use these APIs in a real app.
I didn't give you much time.
[ Applause ]
>> Thanks, Bruce.
All right.
So, let's start with a
demo application here.
We've got an application
that utilizes the
UIViewPropertyAnimator
that utilizes the
UIViewPropertyAnimator
and the additions to
UIViewController transitioning
to create a nice
interruptible custom transition
in a basic app.
So, first, we have
an application
that uses a
UINavigationController
and has a Collection View
Controller inside of it.
And we have this grid of
photos we can scroll through.
When we tap one of the photos,
it will push another view
controller on the navstack.
And that kind of
shows a detailed view
of your photos here.
And if we hit the Back button,
we'll pop that off the navstack.
And alternatively, we can slide
from the left edge of the screen
to do an interactive
transition back.
And this is all available
basically for free
in UINavigationController.
And as Bruce just described,
we also have a rich set of APIs
that allow you to
customize that transition.
And, here, if we want the
photo to kind of zoom up out
of the page, we can do
that, which is pretty cool.
When we hit the Back
button, it'll zoom back down.
And this was all
possible before.
In addition to that,
you could pull
down to start the transition
interactively, move around,
and then let it finish
in an animated fashion.
and then let it finish
in an animated fashion.
So I want to show you
how we've accomplished
that using
UIViewPropertyAnimator.
OK, so the first thing you need
to do is tell the system --
in this case, the
navigation controller --
that you're going to be
providing a custom transition.
So, here, we're going to conform
to the navigation controller's
delegate, and we'll do
so with an
AssetTransitionController.
And that's just an object we've
created that'll implement these
transition protocols.
So, here, we have two methods
that are pretty important.
The first is calling us
for an animation controller
with a particular
operation -- a push or a pop.
We'll save that operation
and we'll return ourselves
since we're going to be
the animation controller.
And then when we return
an object from that API,
the system will call us for
an interaction controller,
at which point we'll also return
ourselves because we're going
to be the interaction
controller as well.
And once we've done that, the
system knows that this is going
to be an interactive transition.
So let's look at
UIViewController
InteractiveTransitioning.
Here, the system is going to
call startInteractiveTransition
on our object with a
transition context,
and that'll have all the
pertinent information
that you'll need to start
your custom transition.
And at this point, we're going
to create a helper object here
that we'll see a little
bit more of in a moment.
But that's the object
that's going
to create our transition
animator.
And we're going to pass the
context to the operation
that we saved
and a panGestureRecognizer
we were using to start
that transition interactively.
In iOS 10, we also allow you to
start an interactive transition
in two phases -- either
an animation phase
or the interactive phase.
So if we did start from
a panGestureRecognizer,
we're going to set this
initiallyInteractive ivar here
to "true" to let the system know
that we're starting
this interactive
transition interactively.
And next, let's look
at the animated transitioning
implementation.
Here, we're not really concerned
about the animation methods.
As Bruce mentioned, we're using
an interaction controller,
and that's going to call
startInteractiveTransition
as opposed to animateTransition.
But the new interruptible
animator API
in iOS 10 is what we're
really interested in here.
And, here, our helper
object that we'll see
in just a second creates
a UIViewPropertyAnimator
and returns that for
us to the system.
So, that's where
the system is going
to add any alongside animations.
And in this demo, you'll see the
navigation bar at the top kind
of animating along
with our transition.
The system is going to take
advantage of this animator
to add those animations.
So let's look at the
transition driver helper object
really quick.
All right.
Here, you saw that we
initialized this object
in startInteractiveTransition.
And that's going to call
through -- excuse me.
There we go.
That's going to call
through to this helper method
with some animations, and
it's going to set up some
of the background
animations for the transition.
You saw visual effect
was animating.
And the two view controllers,
alpha, was kind of animating.
We'll talk about the image
changing its frame a little
later on.
And it'll pass in a completion
closure as well to help clean
And it'll pass in a completion
closure as well to help clean
up some of those
background views.
But, here, let's focus on how
we create that property animator
that we returned to the
interruptibleAnimator API.
So we first get a transition
duration from a helper function,
and we'll look at that
in a second as well.
And we're going to create this
PropertyAnimator with a duration
and a curve of .easeOut.
And we'll pass in those
animations that we were given.
And we'll create a
completion handler here,
and we'll call those completions
that were passed to us.
But the important thing
here is we're going to call
to the transition context
when this animator ends
to inform the system that, "Hey,
we're done with this
transition."
And we can get where we're --
I'm sorry,
the completionTransition
API takes a bool
that we can get the value that
we're supposed to pass through.
So we pass "yes" to that API.
That means we're going
to end the transition.
If we pass "no", it means we're
going to cancel the transition.
So before I go a
little bit further
in the transition animator
helper object here,
I want to show you a few
more features in the demo app
that are made possible by
using UIViewPropertyAnimator.
So if we go back to the demo,
I showed you that you can zoom
out of the screen and zoom back
in hitting the Back button.
But what I didn't show you is
you can pause now midflight.
So we're in the animation
phase, we pause the animation,
and we move into an
interactive phase.
So we're interacting
with this photo
in the middle of
this transition.
When we release our finger,
they'll kind of animate again,
and then we can interrupt
it again.
So we're free to move back and
forth between an animation phase
and an interactive
phase of the transition.
So it's really pretty cool.
So before I go back to the
code and show how we've done
that using the new
view controller APIs,
I want to describe a particular
scenario here that'll help us
understand what the code
is going to be doing.
So imagine we're starting.
We're pulling down with the
panGestureRecognizer here,
starting an interactive
transition.
And then we're going to release
our finger from the screen
and start an animation phase,
and while it's animating,
we're going to pause
midflight and we're going
to interact with it again.
And we're going to
cancel that transition
by pulling it back up.
So that's the scenario that
we're going to describe
when we look at this
next piece of code.
So I've broken that down in
this sample application here
in terms of four methods.
The updateInteraction, which is
the gesture recognizer handler
that's going to update the
interactive transition.
And when our finger lifts,
we're going to call the
endInteraction function.
And then we're going to call
the animate function to animate
to either the begin
or end position.
And then when we
interrupt that animation,
we'll end up calling
pauseAnimation.
So we can do this
loop a few times
in the lifetime of a transition.
We can actually do it as
many times as we'd like.
But in this scenario,
we did it twice.
So we look at updateInteraction.
This is a gesture
recognizer handler.
And when it's called in "state
begin" or "state change",
we're going to look
at the translation
of the gesture recognizer
and we're going to use
that to compute a progress
step that we're going to add
to the transition animator
as fractionComplete.
And then to scrub those
animations in the background --
you saw the navbar in some of
our chrome, the visual effect
in the background
-- it's as simple
as setting the fractionComplete
on this transition animator.
as setting the fractionComplete
on this transition animator.
And that'll let us scrub through
all these animations basically
for free.
And then we'll call to
the transitionContext
to update the interactive
transitions percentComplete.
And then, finally, we have
that image that's
moving around screen.
When we're in the interactive
phase of this transition,
we're going to handle
setting that frame manually.
So we have a little helper
function here to do that.
And finally, we'll
reset the translation
on this gesture recognizer.
So when we're called, when the
finger lifts off the screen,
we'll get a "state ended"
on the gesture recognizer
and we'll call endInteraction.
If we look at endInteraction,
we first just check is our
transition context interactive.
And we want to make sure
it is before we move it
out of the interactive phase.
And we'll call a
little helper function
that tells us whether
we're going to complete
in the begin position of the
animation or the end position.
If we're completing in the
end position, then we're going
to call to the context
to say, "Hey,
we're finishing the
interactive transition here.
We're going to move to
an animation phase."
If we're called with
the begin position,
we're going to say we're
cancelling the interactive phase
and we're going to move
to the animated phase.
And then we just animate.
So let's look at the
animation method here.
So, previously, I told you
that the transition
animator animates kind
of the background chrome
and the alpha transition.
But, here, we're going to create
a second propertyAnimator.
And we're going to use
that to animate the frames
of these images that are
moving around screen.
And the reason we do that is
we might want those animations
to have a different
timing function
than the animations
in the background.
And we'll see that
in just a moment.
So, here, we're just going
to add animations to that.
Now we're going to
specify the end position,
or basically the target or the
initial frame of the image.
And then we're going to start
that property animator
and keep track of it.
Now, remember, we have this
other transition animator that's
living the lifetime
of this transition
that we gave back to the system.
And we just need to make
sure that that's animating
in the correct direction.
So if we specified a toPosition
of "start", then we need
to reverse that animator.
And finally, if this is the
first time that we're calling
through animate here, our
transition animator is going
to be in an inactive state.
So all we need to do is start
that transition animator
So all we need to do is start
that transition animator
and we're off to
the animation phase.
Alternatively, if it's been
started and subsequently paused,
we're going to use
something a little different.
We're going to call in
to continueAnimation
(withTimingParameters
and a durationFactor.
So continueAnimation lets you
continue a paused animation
with different timing parameters
and in different duration,
and the remaining
duration if you'd like.
And here, we're going to
pass "nil", indicating we'd
like to use the timing
parameters that were passed
when we initialized this
transition animator.
But we're going to
change the durationFactor
of the continued animation.
And that will allow us to
synchronize the durations
of this new itemFrameAnimator
we just created
above with the duration
remaining
of this transition animator.
So the durationFactor
is just defined
as 1.0 equals the initial
duration that was specified
when you created this
transition animator.
And that's all we have to do to
kick off the animation phase.
And lastly for this cycle,
let's look at the
pauseAnimation step here.
And this is when it's
animating and we paused.
We put our finger down
on the image view.
We put our finger down
on the image view.
And we just put a
gesture recognizer
on that image view
in the sample app.
And we're free to do that now
because UIViewPropertyAnimator
will hit test the animating
view automatically.
So on that itemFrameAnimator
we just created above,
we're going to stop that
entirely with a parameter
of "true", indicating to the
system that we have no intention
on continuing this animation,
that we'd like the model
values written directly --
excuse me, the presentation
values written directly back
to the model.
And then on the transition
animator, we're just going
to call to pauseAnimation.
Because as we saw in
updateInteraction,
we were scrubbing that animation
when we were interacting
with the image.
And then, finally,
we'll indicate
to the transition
context that we're moving
out of the animation phase
back to the interactive phase.
And so I'd like to show
you one more tip and trick,
kind of a little
detail of the demo here.
We saw that this timing is
kind of slow, and I've done
that for illustration purposes.
But you might want to make
that more natural-feeling
and a little bit more lively.
So if we look at changing
that timing to a spring,
it kind of jumps out of the page
and only the image is
springing into its position.
And here, those background
animations are continuing
to operate on that .easeOut
curve that we specified.
So we were able to
do this change
and make this feel much more
lively with really one line
of code, and we can still
interact uninterrupted.
So let's take a look at that
line of code really quick.
So we come back to
our demo and we look
at our propertyAnimator
convenience method.
We'll notice that we're
specifying a property animator
with timing parameters
that are linear here.
And that's what gives us
this mechanical feeling.
And a colleague of mine
left this great comment
in here specifying some
SpringTimingParameters
to use instead with a
great mass, stiffness,
damping, initial velocity.
And that fully specifies
this spring.
So when we create the
propertyAnimator down here,
this duration that we're
passing in actually isn't used.
Rather, it'll be computed based
on the properties we've
provided in the spring timing.
on the properties we've
provided in the spring timing.
And because of that, we have
animation duration helper
function here that just uses
the propertyAnimator API
that we just saw down below here
to compute the duration for us.
So it basically solves that
spring equation for us.
And we can use that to
match the other duration
of the transition animator.
That's all super simple.
I know this code is looking
a little bit complex,
but it's really only a
few hundred lines of code.
And UIViewPropertyAnimator
made it all possible.
So, with that, I'd like to
give it back over to Bruce.
[ Applause ]
>> Thanks, Mike.
OK, I've got to speed things
up because we're a
little bit over time.
We need to talk a little
bit about hit testing.
We're going to assume that
UserInteractionEnabled is "true"
so that we can actually
hit test our views.
If it was "false", we would
just be swallowing all touches.
We have this property called
isManualHitTestingEnabled
We have this property called
isManualHitTestingEnabled
that defaults to "false".
And the reason it defaults
to "false" is because we want
to be able to hit
test moving views.
If it were "true", which is the
current behavior of the system,
when you try to touch down
on the position of the view
that you saw, we
wouldn't hit test.
And perhaps, puzzlingly, it
would hit test where it's going.
We don't want that to happen
for an interruptible
property animator.
Now in this talk below, which
I recommend that you see,
a technique was given whereby
you could override hit test,
do some calculation to convert
to the presentation layer
and call "super" so that you
could hit test a moving view.
That technique still works.
But now with propertyAnimators
if you have a moving view,
by default, with manual hit
testing enabled to "false",
we're actually going to
hit test against that view.
So that's pretty cool, and
it's really easy to do.
In fact, that's how everything
we've been looking at on stage
so far has been working.
Now this doesn't
work all the time.
Now this doesn't
work all the time.
If you have deep view
hierarchies and so forth,
we are not going to
do the right thing.
So you may need to use
the other technique talked
about in the other talk.
OK. We have this whole
other piece of API
for keyframe animations
that exist in UIKit today.
How do you make those
interruptible?
To recall, a keyframe
animation is basically kind
of like a regular animation
except you specify the path
that you want to animate from.
We want to make those
interruptible as well.
In fact, we want to do
something like what we're seeing
on this video right now.
We want to pause it, scrub it.
And I guess it's
not too surprising.
In fact, you can do it.
It's really easy to do.
You take the existing API
and you add it as a closure
to an animator's animations.
And with that, you have
interruptible keyframe
animations, too.
[ Applause ]
There's one last thing.
I kind of lied.
Interactive
PopGestureRecognizers.
That's the built-in
navigation pop.
Currently, if you have an
interaction controller,
you can't use that.
But new in iOS 10, if you
put a failure requirement
on that gesture recognizer
or your own,
we will allow the built-in pop
gesture recognizer to recognize
and use the built-in navigation
transition alongside your
custom interaction.
[ Applause ]
So, in summary, we've
learned how
to create interruptible
animations
with a UIViewPropertyAnimator.
There's a whole new range
of timing functions
available that you can use.
You can use interruptible
propertyAnimators
to create interruptible
view controller transitions.
For more information,
go to this URL.
There's a couple of other
sessions in UIKit that talk
about other types of animations,
two of which are tomorrow.
I encourage you to go see those.
Thank you.
[ Applause ]