Direct onscreen manipulation is the cornerstone of the user experience on iOS. iOS 10 includes new support for making onscreen interactions even more immersive and interactive. Dive straight into the philosophy and techniques of building completely interactive, interruptible animations in your apps.
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 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 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 .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 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.
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 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'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.
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, 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 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.
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 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.
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. 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.
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.
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 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, 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.
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.
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.
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.
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.
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. 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. 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. 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.
So, let's start with a demo application here.
We've got an application 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.
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 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. 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 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. 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.
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.
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 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. 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.
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.
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.
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.