Custom interactive UINavigationController transition animations in Swift 4

Ludvig Eriksson
3 min readJan 22, 2018

--

The first thing you need in order to create custom transition animations is an animation controller (an object conforming to UIViewControllerAnimatedTransitioning). This object needs to implement two methods.

  1. transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
  2. animateTransition(using transitionContext: UIViewControllerContextTransitioning)

transitionDuration(using:) is just the duration of the animation. animateTransition(using:) is where the actual implementation of the animation takes place. Let's make a simple fading animation, then break down what each part does.

  1. The object needs to inherit from NSObject since UIViewControllerAnimatedTransitioning inherits from NSObjectProtocol.
  2. This property (set through the initializer) let’s us know whether the transition is happening to present a view controller, or to remove one. We will see later how it’s set.
  3. For the duration we return 0.5 seconds.
  4. To get the views we are animating, you can call view(forKey:) on the transitionContext object. There is also viewController(forKey:) if you need to access the view controllers to communicate with them.
  5. Here we set the initial state of the animation. It is also our responsibility to add the toView to the view hierarchy. We add it to the containerView of the transitionContext object.
  6. Inside UIView's block based API we set the final state of the animation. For the fade animation we simply fade in toView or fade out fromView, but again, this could be anything animatable.
  7. When the animation is complete there are a few things left to do. First, if the transition was cancelled, we are responsible for undoing any changes to the view hierarchy we previously made, so we remove the toView from the view hierarchy. Second, we must call completeTransition(_:) to let the context know that we have finished animating.

Now, how do we let iOS know to use this animation instead of the default one? For that we need to implement a method in UINavigationControllerDelegate called navigationController(_:animationControllerFor:from:to:). A simple way to achieve this is to subclass UINavigationController and set its delegate to itself. Let's look at how to do that.

  1. We create a custom subclass of UINavigationController.
  2. Set the delegate of the navigation controller to itself.
  3. Implement navigationController(_:animationControllerFor:from:to:), returning an instance of the animation controller we just created. We can use the operation object to see if it's a push or pop transition, so that the animation can be customized based on this.

That’s great, you say, but now the swipe-back gesture no longer works! On to the next point.

Making it interactive

In order to make the transition interactive you need two additional things.

  1. Something to drive the progress of the transition.
  2. An interaction controller (an object conforming to UIViewControllerInteractiveTransitioning).

We are going to use a UIGestureRecognizer, or more specifically a UIScreenEdgePanGestureRecognizer to drive the transition. This doesn't have to be a gesture recognizer, but anything that can relate to the transition progress. Let's see how this can be done.

First, add the following properties to CustomNavigationController.

Configure the gesture recognizer in viewDidLoad().

Add the handler for the gesture recognizer.

  1. First we calculate how far the user has swiped.
  2. If the gesture just began, we create the interaction controller, then begin the transition by calling popViewController(animated:).
  3. Whenever the progress changes, we call update(_:) on the interaction controller.
  4. When the gesture is complete, we tell the interaction controller to either finish the transition or cancel it, with the finish() and cancel() methods respectively. Finally we set the controller back to nil. We will see why shortly.

Just as with the animation controllers, we need to let iOS know to use this transition controller. That is done by implementing another UINavigationControllerDelegate method, namely navigationController(_:interactionControllerFor:). So, let's implement that in our CustomNavigationController.

By setting the controller back to nil earlier, this method will simply return nil when a non-interactive transition is happening.

Conclusion

And there you have it, a custom, interactive UINavigationController transition animation. To quickly see it in action, check out this demo project. If you have any further questions, let me know. You can reach me on Twitter (@ludvigerikss0n), or send me a good old email.

--

--