Simple, custom UINavigationController transitions

How to easily implement custom transitioning for navigating between view controllers

Recently I have started working on a new personal project where I had a static background for the entire application and all my view controllers’ views had a transparent background.

Default transition animation overlaps a little bit the source and destination views, therefore, there is only one way to make our application navigate with some dignity and smoothness — that’s right — it is custom animated transitioning and since we usually want to keep swipe to back gesture on view controllers we also have to create custom interactive transitioning as well.

Sample Project

I have created a sample project — you can find it on GitHub.

Let’s Get Theoretical

The first thing we have to do is implement custom transition for our UINavigationController and implement it’s delegate method

which provides an object that is responsible for providing information on how our custom transition will animate source and destination views while navigating between them.

To add user interaction to a view controller transition we have to implement another method (also will be needed to handle back gesture properly) is

Both methods are depending on each other while creating custom, interactive transition and are available since iOS 7.0 so pretty much all new or updated applications can get benefits from them.

Let’s get Practical!

First of all, we have to create a new object that will be implementing UIViewControllerAnimatedTransitioning protocol.

Since mentioned protocol conforms to NSObjectProtocol our animator object have to conform to it as well and the easier way to achieve that is to simply inherit from NSObject. UIViewControllerAnimatedTransitioning requires two methods which you can see above as well as there are two optional methods which we will not need in our project today so let’s not get interrupted by them.

Our custom animator object needs to know if the transition is invoked by a push (being presented) or a pop (being dismissed) action of navigation controller so we will add Bool property which will store that information. Since we will create a quite simple custom animation we will not need any additional properties — if you want to create more complex animation you should store here everything that will be needed for your animation (ie. you could store here frame of a tapped button so you can animate present another controller from that point).

As an example, I have created a simple “fade and slide/move” animation. I will explain each part of my TransitionAnimator below.

  1. Property that indicates if the animator is being responsible for a push or a pop navigation.
  2. It’s an initializer.
  3. You can set any custom duration as you wish. I have used constant from UINavigationController to fit other (hiding/showing bar) animations in the application.
  4. We need a controller’s views that will be animated and we can access them through UIViewControllerContextTransitioning’s method view(forKey: ). If needed there is also an option to access the whole view controller, just use viewController(forKey: )instead.
  5. Since we already defined our duration in another method simply we access it here.
  6. We access containerView here which acts as the superview for the views involved in the transition. Depending if we present or dismiss view controller we either add destination view to our container or add it below source (fromView) view.
  7. Here we set an initial frame for our destination view which in my sample it is being a little bit to the left or right of source view — that will allow us to create smooth slide/move animation. Also, it is the place where “magic” happens — create any animation you want here. I have created fade animation for first half part of the whole animation and slide/move animation for the whole duration of it.
  8. In case of transition being canceled we have to clean up everything that we have done during the process — I simply remove added destination view from the container.

Wow, that was pretty easy, wasn’t it? 😎 Now it is time to attach our custom transition to our navigation controller — how to do that? Well, there is few possible ways to go but we want to stay Swifty! To achieve that let’s choose a little bit harder (is it?) path and instead of inheriting from either UINavigationController or UIViewController let’s create TransitionCoordinator (not sure if the name fits here but that is how I called it for now, if you have any suggestions, leave it in comments below). Our coordinator should conform to UINavigationControllerDelegate protocol (Hello NSObject again).

  1. Need that to handle back-gesture properly — it will be set by gesture recognizer but we will get back to it later.
  2. You probably remember theory I mentioned earlier to won’t get into details — we return here our custom animator depending on operation being either pop or push.
  3. Returns interactionController to handle interactive transitioning.

Finally, the fun begins! As I mentioned we do not want to inherit from anything and we want to be able to easily attach our custom transition to any UINavigationController we want, to do that let’s create an extension.

  1. Just a static key which will be used to associate anobject (read about that ie. here).
  2. A computed property that will return our associated TransitionCoordinator object.
  3. Create an instance of TransitionCoordinator and associate it with the mentioned key.
  4. Set associated object as a delegate of UINavigationController.
  5. Let’s create an instance of UIScreenEdgePanGestureRecognizer which will react to the left edge only and add it to the navigation controller’s view.
  6. Here we handle our edge-swipe gesture. There are few cases — once we begin our gesture we create an instance of UIPercentDrivenInteractiveTransition and start popping our controller. On finger move, we update the progress of back gesture and finally, on gesture’s end we either finish the transition (if we moved controller by at least a half-width and gesture was not canceled in meantime) or cancel it — in both cases, we clear the interactionController.

And…

That is all.

We are done with the hard work. Now to make the magic happen let’s just call our extension’s method on any navigation controller we want (navigationController.addCustomTransitioning()).

If you have any questions you can contact me by email, Twitter, create an issue on GitHub or just simply leave a comment below ;)

Thank you for reading.

Newsletter

Do you want to hear more from me? Do not skip any articles — sign up for the newsletter.