Transitioning
I like to kick the morning off by visiting a handful of sites that collect cool app designs. One of my favorite is Capptivate. I often find myself browsing the site thinking “I want one of that.”. It’s even better if the design triggers another question in my mind: “How did they do that?!”.
I had one of these moments last week, looking at the push transition in Facebook’s Paper app. It was a good chance to take a stab at iOS7 custom transitions.
Learning custom transitions
I won’t be covering in depth the custom transition APIs in this post, just what I needed to learn to reproduce the aforementioned effect. The main reason is because there are already good sources that cover this matter, I learned a lot from NSScreencast’s “iOS7 View controller Transitions” (the subscription fee is well worth all the quality content) and objc.io’s “View controller transitions”. The former covers modal transitions, while the later coverage of push transitions was really helpful.
UINavigationController’s delegate
iOS7 introduces a new set of delegate methods in UINavigationController’s protocol:
1 2 3 4 |
|
The first one is the most interesting of the bunch, it’s called when the navigation controller is ready to animate to a new state (or for better terms, when it’s ready to perform a new UINavigationControllerOperation
) and expects in return a new object that conforms to the UIViewControllerAnimatedTransitioning
protocol. It also has a reference to the view controller that we are transitioning from and a reference to the controller that we are transitioning to:
1 2 3 4 5 |
|
UIViewControllerAnimatedTransitioning
Cool, we get notified when the navigation controller is ready to make its magic. Let’s take a look at what kind of object it expects from us, by inspecting UIViewControllerAnimatedTransitioning
protocol:
1 2 3 4 5 6 7 8 9 10 |
|
Easy enough… one call for the animation’s duration and one call to perform said animation.
Before diving into the code we might want to lay out our animation graphically. Let’s fire up Sketch and draw something:
Ok, so we have two view controllers, they both have table views, and we want to present the new cells with a slight delay each, while kicking the old cells to the other side, with the same delay.
Let’s break it down.
From ViewController:
- Get the visible cells in the table view
- Move each cell to the left by the screen width
- Fade out each cell
- On completion set the cells back to their position (we’ll need them on pop)
To ViewController:
- Set the controller’s frame to match the starting controller
- Move the controller’s frame to the side
- Get the visible cells in the table view
- Push the cells to the right with no animation
- Move each cell to the left
That looks pretty simple, but there are couple of caveats.
The first thing that I noticed is that the new cells are not available at first. The second issue was that if I moved the destination frame to match the source one right away, the source cells would disappear instantly.
To solve these issues I had to use a little trick: use a 0
duration animation where I move the frame of the destination controller to the point (1,0)
. This allows the destination controller to load its cells, and the source controller will still be visible.
To sum it up (I’ll include only the push code):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
|
This will get the cell animating back and forth, but the effect is far from impressive, we need some further tinkering.
Setting up the view controller
If you take a look at Paper, the cells move back and forth on the same background image, you can’t see a clear cut from a view controller to the other. That’s easy to achieve, we just need to configure our navigation controller like this:
1 2 3 4 5 |
|
And our cells need to be transparent too:
1 2 3 4 5 6 7 |
|
Now we can take the new animator for a spin, by returning it in the delegate method:
navigationController:animationControllerForOperation:fromViewController:toViewController:
Here’s the result:
It’s not perfect, but it can be easily tweaked.
The code
I released all the code described above as a pod. It includes both the push and pop operations, and its fairly customizable.
You can find the source here.
Until next time.