In the first few steps of this series, we’ve talked a bit about the idea that the success of any app — and especially a small indie project — often hinges on the quality of execution. An app that is thoughtfully designed and makes obvious the amount of care and polish that went into building it likely stands a much better chance of capturing some real interest than one that feels same-y or slapped together.
This week, I want to start looking at an aspect of UIKit development that plays a huge role in creating that feeling of polish and care, but that, in the past, I’ve found to be a bit of a pain to work with: custom view controller transitions!
In building my definitely-soon-to-be-critically-acclaimed indie app, a big goal of mine has been to try not to compromise on design ideas because of complications or inconveniences in implementation. One place UIKit makes it really tempting to compromise is in presenting and dismissing view controllers. Do you really want to have to figure out what makes subclassing
UIPercentDrivenInteractiveTransition different from implementing your own class conforming to
UIViewControllerInteractiveTransitioning…or would you rather just call
present(_:animated:) and move onto the fun stuff?
Well, in the name of lofty design ideals, I decided that custom view controller transitions ARE the “fun stuff”, damn it, and I hunkered down and endeavoured to truly, totally, finally wrap my head around this whole clustercuss of classes and protocols. Although I’m not sure I’ll ever feel like I’ve quite reached the summit of this treacherous climb…I’ve built some cool stuff and learned a bunch along the way. Let’s get into it!
What this is, and what it is not
This is really just meant to be a primer on the roles of each of the entities involved in a custom view controller transition — there’s a lot to keep track of, and in trying to master this stuff, I personally found it really helpful to take the time to step back and fundamentally understand what each of the relevant classes and protocols are responsible for before jumping into building anything. This isn’t a step-by-step tutorial with code examples — there are plenty of those out there — but rather, an attempt to explain the basic building blocks of custom view controller transitions in a way that’s useful, easy to digest and quick to refer to when things start to get blurry. Basically, it’s what I wrote for myself to make sure I fully understood what I was doing, and wasn’t depending on anything that felt like magic.
Note from the future: I’ve written a series of 3 articles on some more concrete, advanced topics having to do with custom view controller transitions. If this article is a bit too simple for you, feel free to jump ahead! Here’s the first one.
Before getting started, I have one more important thing to say:
Read the documentation! It’s quite good!
Because custom transitions can feel pretty overwhelmingly complicated, and the docs are admittedly a bit dense, it can be tempting to skip them and just go hunting for a tutorial that’s going to hand you all the code you need. What I’ve found is that a lot of the tutorials out there do things quite differently from one another, and they often struggle to explain why exactly they’re doing things in a particular way, or how their implementation might be tweaked or extended to do different or more complex things. Apple’s own documentation might not give you a bunch of code you can paste directly into your project, but it will help you understand what the cuss is actually going on.
So, consider this to be a concise companion guide to Apple’s helpful but heavy documentation. It’s not a replacement, and neither is any tutorial you find on the internet. Actually, that’s true of pretty much any development topic, but I digress. Onwards!
The anatomy of a custom view controller transition
Note: I’m dealing specifically with custom modal transitions here; a lot of this is also applicable to customizing navigation controller transitions, for example, but that’s not really within the scope of this article.
Presenting view controller & presented view controller
This terminology isn’t unique to custom transitions, but it is important to get right, so let’s make sure we’re on the same page. Imagine you have a HomeViewController, and from there, you present a WarningViewController. HomeViewController is the presenting VC, and WarningViewController is the presented VC. Easy peasy.
In the context of a custom transition, the container view is a view that’s created and managed by UIKit, and that contains both the presenting and presented views during the transition. If your transition includes any custom views that aren’t part of the presenting or presented views, you’ll add them to the container view as well.
Your implementation of this protocol is known as an animation controller, and is responsible for actually animating the transition between view controllers. It has access to both the presenting and presented view controllers, which allows it to animate their view frames into place, or to manipulate other properties of the view controllers and their views. It also has access to the transition’s container view if needed. You could implement two animation controllers to handle presentation and dismissal separately, but it often makes sense to have a single animation controller that covers both presentation and dismissal animations, and that knows which direction to perform the animation in based on a simple
A concrete implementation of this protocol is known as an interaction controller. An interaction controller is only necessary if you want to allow your transition to be driven by some sort of gesture. A simple example of this is a case where you want to be able to dismiss your presented view controller with a swipe, and have the view’s movement correspond with your finger’s movement. UIKit provides a specific implementation of this protocol —
UIPercentDrivenInteractiveTransition — that, given continuous updates about the gesture as it’s performed, can automatically use your existing animation controller to figure out where your animated views should be throughout the gesture. You can also implement your own interaction controller to get more fine-grained control over the interactive transition. Either way, an interaction controller needs to be fed continuous updates on the state of the gesture, and is responsible for translating these into visual updates to the involved views. Like animation controllers, interaction controllers have access to both the presenting and presented view controllers, as well as the transition’s container view, giving you precise control over how the interaction impacts the various views on screen.
Every view controller transition — custom or standard — is driven by a presentation controller that’s responsible for managing the whole thing, from the start of presentation to the end of dismissal. A presentation controller has direct access to the presenting and presented view controllers and the transition’s container view, and knows about when presentations and dismissals begin and end. Although a custom presentation controller isn’t necessarily required to build simple custom view controller transitions, it’s typically needed when you want to add decoration views to the container view during transition (e.g. a dimming view: darkening the screen behind some sort of popup), or when you want some dynamic control over the size and positioning of the presented view (e.g. responding to changes in content or screen size).
The transition context provides contextual information about the transition as it occurs. Remember how I said that both animation controllers and interaction controllers have access to the presenting and presented view controllers? This is true thanks to the transition context — both animation and interaction controllers implement methods that are called by UIKit and given a transition context to work with. The transition context provides access to the views and view controllers involved in the transition, along with details about the transition, such as what the frame of the presented view controller should be at the end of the transition, or whether the transition is currently being driven interactively. Interaction controllers also use the transition context to report the interaction’s progress and completion to UIKit.
The transitioning delegate is really what ties together everything described above. Your transitioning delegate is responsible for vending the various objects that are needed to perform your custom transition: a custom presentation controller, animation controller(s) and interaction controller(s). In other words, it needs to know how to create or otherwise have access to each of these controllers. In simple examples, you’ll often see the presenting view controller playing the role of transitioning delegate, although creating a dedicated class is often a better approach. With a transitioning delegate in place, we can now instruct UIKit to use our custom transition by simply assigning the transitioning delegate to the VC we want to present, and setting the modal presentation style to
So how exactly does it all fit together?
It’s tempting to want a single, “correct” answer to this question. How do I know if I’m doing it right? Unfortunately, like pretty much anything in software development, there are lots of different ways to actually piece together your custom transitions, each with their own set of trade-offs. Nonetheless, to provide a bit of clarity, let’s step through some simplified presentation and dismissal cases to start to get a sense for how the various entities behave, and how they communicate with each other.
We want a
WarningViewController to be displayed on-screen using a custom transition when a button is tapped in
HomeViewController conforms to
UIViewControllerTransitioningDelegate, so we can use it as the transitioning delegate for our custom transition.
Our custom transition begins the moment
present(_:animated:) is called. I’ve written a short, bad play about it, because it’s honestly a much more readable way to describe the flow of information than dryly describing every technical step 🎭
[Applause, curtains rise]
Oh hey UIKit, can you help me present this
Yeah, let’s do it. Okay, says here you’re the transitioning delegate, so…do you have a presentation controller for me?
Yep, here’s a
Perfect, thanks. [Starts setting things up behind the scenes by creating the container view, transition context, etc.] Okay, I’m also going to need an animation controller to actually present this thing.
Oh right, here’s a
FancyWarningAnimationControlleryou can use. I’ve made sure it knows to present rather than dismiss.
Good, good. Do you have an interaction controller for me as well?
Nah, let’s just present this one non-interactively.
Sure! Let’s get started then. Hey presentation controller, FYI: we’re about to start the transition.
Got it. [Adds a dimming view to the container view.] Hey UIKit, can you make sure this dimming view fades in at the same time the rest of the presentation animation is happening?
Yep, no problem. Also, where do we want the
WarningViewControllerto end up on screen? The animation controller’s going to want to know.
Let’s just have it cover the bottom half of the screen.
Works for me. Okay, animation controller? You’re up. Everything you need should be here in this transition context. Let me know when you’re done.
Roger that. [Beautifully animates
WarningViewControllerinto view.] And….done.
Nailed it. Thanks everyone!
Let’s assume a pan gesture recognizer is set up on our
WarningViewController’s view to handle the dismissal interaction. Where the gesture handler is actually implemented isn’t terribly important, as long as it has a direct line to the interaction controller. As soon as the gesture begins, we call
dismiss(animated:), and thus begins our second short play about an interactive dismissal.
[A clear, crisp fall morning. The old Mac startup sound plays off in the distance.]
Hey, uhh…UIKit, is it? Weird question, but: can you dismiss me?
Yeah, no problem. I see here that your transitioning delegate is
HomeViewController— oh yeah, I remember helping them present you a while back. I’ll get in touch with them and we’ll have you dismissed in no time.
[UIKit picks up the phone and calls
Hey, me again.
WarningViewControllerneeds to be dismissed, and since you’re their transitioning delegate, I’ll need a few things from you again.
Oh yeah no problem! You still have that presentation controller I gave you, right?
Yep, I’ve been holding onto that.
Okay, here’s another
FancyWarningAnimationControllerfor you to use — this one’s set to dismiss. And, I know
WarningViewControlleronly ever dismisses interactively, so you should take this
UIPercentDrivenInteractiveTransitionas well — there’s already a gesture handler set up who knows how to use this.
Brilliant! Okay, let’s make it happen. Presentation controller, we’re about to start the transition.
Sounds good. I’m going to fade out this dimming view — can you make sure the timing lines up nicely with the gesture?
Will do. Now, animation controller, interaction controller, here are your transition contexts. Do your thing!
UIPercentDrivenInteractiveTransition, and the gesture handler all huddle together. As the pan gesture progresses, the gesture handler reports progress to
UIPercentDrivenInteractiveTransition, who works with
FancyWarningAnimationController to figure out what the views involved in the transition should look like at the given progress level. As a team, they continuously update
WarningViewController’s position to reflect the progress of the dismissal. It’s a sight to behold. Eventually…]
Gesture handler: And……DONE!
FancyWarningAnimationController completes the dismissal animation and gives UIKit the good news. They all look at each other contentedly. Their work here is done.]
That may have gotten a bit silly and oversimplified, but honestly, when you’re new to this stuff, it’s already a lot to take in, and I needed to wrap this up. There are a lot of moving parts here, they all interact with each other in less-than-obvious ways, and it can be hard to figure out exactly who should be responsible for what. As someone who is in the midst of building what could very well be my final full-fledged UIKit project, I wanted to make sure I had a really firm understanding of this whole view controller transitioning API, and could properly make use of it to build exactly what I wanted. I hope sharing some of that knowledge in a lighthearted way helped clarify some things for a few folks out there! If, on the other hand, you:
a) think this whole thing still seems wildly overcomplicated, and
b) are able and willing to build whatever it is you’re building with a promising, but young and somewhat janky new set of tools…
SwiftUI might be looking pretty good to you right about now 😏
Thanks for reading! This is a relatively quick look at a pretty big topic, so if you need some help or have any follow-up questions, Let me know! Find me on Twitter here 🐦