This is the second in a series on navigation transitions. In the first post, we simplified the API by ignoring most of it, and just composing a few parts.
Today, we’ll review the implementation in Locket Photos of a custom navigation transition: a simple, self-contained modal transition. (We’ll get into a much-more-complex transition in a future post.)
There are two screens in this transition: the photo grid, and the presented modal card. Before Locket’s v1.3 update, I was using a simple crossfade animation, provided by UIKit. It looked like this:
This was… okay, but not something I felt super-proud to ship! What I really wanted was the background to fade in, and the card to rise up from the bottom, with a springy feel. On dismissal, I’d want the reverse to occur, with a different animation timing. Here’s what shipped in v1.3:
Composing the Animation
Remember our diagram from last time? Let’s build it!
We’re presenting a modal screen, and we want to customize the animation. The presenting screen doesn’t need to know anything about this custom animation - the modal will handle all of that internally! 1
In our presenting screen, we call this:
…and the rest is all handled by our ModalCardViewController!
Next, let’s extend ModalCardViewController so it conforms to our animation protocols:
Note: Today, we’ll be building these protocol conformances directly into the view controller. Yes, the architecture would be cleaner to break them out into a separate class, perhaps called “ModalAnimationController”. However: I’m intentionally keeping everything in one place, to simplify this introductory example. In the more-complex implementation later in this series, we’ll break these animation protocol conformances out into separate classes.) 2
First, we need to implement
UIViewControllerTransitioningDelegate (i.e. “the vending machine that dispenses navigation animations”). We’ll need to vend an animation controller for the presentation and dismissal transitions.
(You’ll note that in this code, I’m setting a
currentModalTransitionType on my presented view controller — this is a little enum I’ve defined, to aid in simplifying the process of figuring out which animation to run for a given transition.3)
…and now, all that’s left is to write out the animation code itself!
A recommendation for UIViewPropertyAnimator
Here’s why I recommend this API: I believe that
UIViewPropertyAnimator makes it far easier to build non-trivial custom navigation transitions:
- it simplifies coordination of animations in disparate areas of your code, via
- you can modify, cancel, and reverse the animation, which is essential for interactive, gesture-driven transitions,
- timing parameters are easier to define and use.
One of the hardest parts (for me at least!) in building out complex navigation transitions is keeping my code properly scoped: I prefer to keep my subviews
UIViewPropertyAnimator makes it much easier to keep ‘em that way. When I’m able to build animations while keeping the guts of my views
private, I’m much happier with the process and the finished product.
For today’s implementation, we could just-as-easily use
UIView.animateWithDuration, but instead, let’s practice using
UIViewPropertyAnimator, so it’s not scary when we see it next time!
Writing the animations
Let’s implement the second protocol,
UIViewControllerAnimatedTransitioning (i.e. “the dang animation itself”). We’ll write out these animations with
UIViewPropertyAnimator, as recommended above!
We want the background to fade in, and the card to slide up from the bottom — and the reverse when the modal is dismissed. The only thing that differs between presentation and dismissal are the animations’ timing curves — the rest of the code can be shared between the two! Here’s the animation code:
Hand-tuning the animations
To make animations truly wonderful, you’ve got to hand-tune ‘em on-device. Animations that look great on your laptop often feel too slow when in-hand.
The finished product
Today, we’ve composed our very first custom navigation animation - nice work! 🥳
We built a self-contained modal presentation animation (and the dismissal animation), hand-tuned ‘em on-device using SwiftTweaks, and everything’s composed in a way that keeps our modal’s subviews
fileprivate . We also learned why
UIViewPropertyAnimator is a really great tool for custom navigation transitions.
There are two more posts in this series. Next, we build out a complex, non-interactive push/pop animation, like the one in the Photos app, with all the trimmings and little details you’d want in a polished implementation. Then, in the last post, we’ll build Photos’ interactive drag-to-dismiss transition.
This is kinda like how
UIAlertControllerhandles its own animated transitions — you just present it from your code, and it handles the animations! ↩
Think of it like building a
UITableView: sure, you can (and often should!) break out
UITableViewDataSourcefrom the table’s view controller — but you and I both know that it’s often completely fine to start by building them in the view controller’s code, unless-and-until we feel it’s in need of a refactor! 😘 ↩