UIKit Animation Part 3 (ViewController Custom Transition)

Ali Akhtar
14 min readMay 3, 2021
https://www.raywenderlich.com/5304228-ios-animation-tutorial-getting-started

In this part we will see how we can create custom Modal View controller transition animation

Customizing Modal Transition Animations

Transition animations provide visual feedback about changes to your app’s interface. UIKit provides a set of standard transition styles to use when presenting view controllers, and you can supplement the standard transitions with custom transitions of your own.

As shown in Figure 1 we have two View controller (let’s name it as VC1 and VC2 respectively ) when user tap on Red view of VC1 we will redirect user to VC2

Figure 1

As shown in Gif 1 this is the standard modal animation, and if you can see user can lose its sense between these transition. Basic purpose is to traverse different or several viewcontroller without losing your sense of place . Like calendar app , selecting month will cause that month to scale up to the fill screen and selecting a day will cause the surrounding week to slide up and reveal details of that day schedule

Rule of Thumb

Transition should show relationship between view controllers

Gif 1

Transition animation

A transition animation swaps the contents of one view controller for the contents of another. There are two types of transitions: presentations and dismissals. A presentation transition adds a new view controller to your app’s view controller hierarchy, whereas a dismissal transition removes one or more view controllers from the hierarchy.

It takes many objects to implement a transition animation. UIKit provides default versions of all of the objects involved in transitions, and you can customize all of them or only a subset. If you choose the right set of objects, you should be able to create your animations with only a small amount of code. Even animations that include interactions can be implemented easily if you take advantage of the existing code that UIKit provides

Getting Started

Mainly there are two protocol you will used to create transition animation

Protocol 1 UIViewControllerAnimatedTransitioning

An object conforming to it is called an animator. It gives UIKit some essential information via two methods

  1. transitionDuration
  2. animateTransition

Our goal in this transition animation is to make red View in VC1 scale up in place and transform into the red view of VC2

As shown in Figure 2 few things to note

  1. We created animator object , Note we subclass from NSObject since the protocol we need to conform on next step is Objective — C Protocol
  2. Extend custom animator to conform to UIViewControllerAnimatedTransitioning obj-c protocol
  3. First method is func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval (As the name suggests how long in seconds the transition lasts, that time interval we need to return). In our case we specify 1 second so transition between VC1 to VC2 when presenting will happen within 1 sec
  4. The second method is animateTransition , This is where all of your code will go
Figure 2

Protocol 2 UIViewControllerTransitioningDelegate

  1. Every-time you present or dismiss new view controller UIKit ask it delegate whether or not it should use a custom transition, if you set vc.transitioningDelegate = self, if it is not set these optional delegate will not call so it will do the standard animation
  2. func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) is for to present a new controller
  3. func animationController(forDismissed dismissed: UIViewController) is for dismiss
  4. Each of these methods need to provide UIKit with an animator object
  5. If you return nil and you set vc.transitioningDelegate = self it will use standard animation
Figure 3

Our Goal

Our goal is when user tap on view , we want the view in VC1 to appear like it is scaling up and transition into detail view, we need to give this effect so user can’t loose their sense

Create Animation / Animator Object

All the animation code will go into the animate transition method, some theory t

  1. Every transition happens with the help of transition context . The context transitioning object passed to your animateTransition: method contains the data to use when performing your animations. Never use your own cached information or fetch information from your view controllers when you can get more up-to-date information from the context transitioning object.
  2. The context provides a container view which you can think of the super view for all views used in the transition
  3. The context also gives you access to the paramaters and view controllers of the transition
  4. Once a transition to new view controller is triggered, the existing view is added to the container view for you and the new view controller is created for you
  5. Your only task in all of that is to add the new view to the transition container view then animate both views to create any kind of transition animation you like

As shown in Figure 4 we get UINavigationController as from View key 😕, we expect VC1

Figure 4

We will do later how custom animation will work with controller having container like navigation controller or tab bar, for now let’s delete navigation controller

Figure 5
  1. As shown in Figure 6 , now our problem solved transitionContext.viewController(forKey: .from) return actual fromViewController which is VC1,
  2. transitionContext.view(forKey: .to)?.subviews return subview on VC2 which has view taking whole screen (0 0; 428 869) and there are some weird subviews
  3. But transitionContext.view(forKey: .from) return nil Note: iOS 8 introduces a new method and keys for accessing the fromView and toView
Figure 6

Set vc.modalPresentationStyle = .fullScreen, we will do reason explanation later

Figure 7

Finally we get everything right, let’s discuss

  1. transitionContext.viewController(forKey: .from)! return VC1 reference we can access everything with this reference cool in our case it is ViewController
  2. transitionContext.viewController(forKey: .from)!.view , it will return the main View of VC1 which basically in whole screen root View, (0 0; 428 926); Note : screen width = 428 and screen height = 926
  3. transitionContext.viewController(forKey: .from)!.view.subviews. As we know VC1 only have one red View added as subview frame (0 47; 428 132) Note: (x y, width height)
  4. transitionContext.view(forKey: .from)! you can get direct main / container View of VC1 using this method as well (0 0; 428 926) you can match the reference as well
  5. po transitionContext.view(forKey: .from)!.subviews same it will return Red view in VC1
  6. transitionContext.containerView. return the superview for the animations. Add all key subviews to this view.(later you know) For example, during a presentation, add the presented view controller’s view to this view.
  7. transitionContext.containerView.subviews. return only VC1 main view which is <UIView: 0x7fed9de0a8f0; frame = (0 0; 428 926) , why container don’t have VC2 (later you know)
  8. transitionContext.containerView.subviews[0].subviews. VC1 red view is automatically added in container View
  9. You can also get toView as transitionContext.view(forKey: .to)!

Note: Container View is UITransitionView which is internal to Apple SDK, The purpose of container view View to help transition animation by put everything in one container . The container view acts as the superview for the views involved in the transition (including those of the presenting (VC1) and presented view controllers (VC2)) during the animation sequence. UIKit sets this view for you and automatically adds the view of the presenting view controller to it. The animator object is responsible for adding the view of the presented view controller (in our case VC2), and the animator object or presentation controller must use this view as the container for all other views involved in the transition

Figure 8

Same way you can access to View as well (VC2)

Figure 9

Plan Present animation

I said that we wanted to start detail view at the same size and position of the redview in VC1 and scale up to fill the screen, To do so we need to find the size of red view . Few things to note

Animation is moving object from starting value to ending value over period of time

  1. In planning animation our three question would be , which view to animate (VC2), which properties (scale and center / position), Their inital and final Value (Initial: VC1 red View scale and position Final: VC2 main View Value)
  2. Since we need to animate VC2 we first initialise it’s state
  3. fromVC.view.subviews[0].frame we get the reference of red view and get it’s frame as initial frame , and for position we use center , if you want to learn scaling through transform you can read this blog
  4. containerView.addSubview(toVC.view) This step is very important . as we said earlier The container view acts as the superview of all other views (including those of the presenting (VC1) and presented view controllers (VC2)) during the animation sequence. UIKit sets this view for you and automatically adds the view of the presenting view controller to it. The animator object is responsible for adding the view of the presented view controller, In our case presented view controller, is VC2 view we need to add it to container view . also this is the reason transitionContext.containerView.subviews we didn’t get VC2 views
  5. Then we use UIView.animate (familiar method we see inn part 2) You can see the Gif 2 final result , Now we are not losing user sense
  6. Final and also very important transitionContext.completeTransition(true). Calling the completeTransition: method at the end of your animations is required. UIKit does not end the transition process, and thereby return control to your app, until you call that method. . You can check it’s important just comment it and you will see VC2 will never dismiss since you didn’t tell UIKit

Math:

  • VC1 red frame Initial width and height = 428, 132, VC2 main View width and height = 428, 926
  • Scale = Initial / final , Scale X = 428 / 428 = 1 so no change, Scale Y : 926 / 132 = 7 times bigger
  • Center is very important if you don’t set initial center same as red view, transition will start from center of screen , you can check just comment center logic from two places
  • In animation toVC.view.transform = .identity since we set the initial state of VC2 we need to reset to previous which in our case it end value , if you want more detail plz refer previous blog
Figure 10
Gif 2

Plan Dismiss animation

When we tap on VC2, it should shrink down into it;s place and and become the same size as red view. In planning animation our three question would be , which view to animate again(VC2), which properties (scale and center / position), Their initial and final Value (Initial: VC2 scale and position Final: red View Value)

as shown in Figure 11, we added one property to identify weather transition is present or dismiss

Figure 11

The context transitioning object uses “from” and “to” nomenclature to identify the view controllers, views, and frame rectangles involved in a transition. The “from” view controller is always the one whose view is onscreen at the beginning of the transition, and the “to” view controller is the one whose view will be visible at the end of the transition. As you can see in Figure 12, the “from” and “to” view controllers swap positions between a presentation and a dismissal. It’s very important

Figure 12

As shown in Figure 13 , from is now VC2 and to is now VC1 and now VC1 views are not added in container view , we need to add it

Figure 13

Few things to talk

  1. We get VC2 and VC1 main view using transitionContext.view(forKey method A/C to condition whether it is presenting or dismissing. Note currently we are using CustomAnimator for both presenting and dismissing we can also create separate class for both and provide class in respective delegate. (func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) and func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?)
  2. We get the container as usual it is super view
  3. Then we prepare inital Frame and finalFrame A/c to presenting condition . Note since we are animating VC2 in both presenting and dismissal. when presenting we scale down VC2 view to red view as initial state , but for dismissal VC2 main view will start it’s original frame so we directly use it’s frame
  4. Then containerView.addSubview(toView) we should at-least add toView when presenting animation otherwise animation will not work (In short presentedViewController view should add) , You can will come to know this later
  5. containerView.bringSubviewToFront(VC2) This is very important we bring VC2 main View to front , comment this line and you will not see dismiss animation since in container view hierarchy while transiting first is VC2 then VC1 main view so VC2 dismiss animation overlap by VC1 main View
  6. Then we animate if presenting user identity and dismiss we need to scale down VC2 main view to red View , final result is in Gif 3
Figure 14

As shown in Gif 3 user can’t imagine there are two screen involve in this animation , so user will not loose its sense when there is a transition animation going on super cool !

Gif 3

Behind the scenes of custom transitions

  1. UIKit lets you customize your view controller’s presentation via the delegate pattern; you simply make your main view controller (or another class you create specifically for that purpose) adopt UIViewControllerTransitioningDelegate.
    Every time you present a new view controller, UIKit asks its delegate whether or not it should use a custom transition. Here’s what the first step of the custom transitioning dance looks like:”
  2. UIKit calls animationController(forPresented:presenting:source:) to see if a UIViewControllerAnimatedTransitioning is returned. If that method returns nil, UIKit uses the built-in transition. If UIKit receives a UIViewControllerAnimatedTransitioning object instead, then UIKit uses that object as the animation controller for the transition.”
  3. “UIKit first asks your animation controller (simply known as the animator) for the transition duration in seconds, ”
  4. “then calls animateTransition(using:) on it. This is when your custom animation gets to take center stage.
    In animateTransition(using:), you have access to both the current view controller on the screen as well as the new view controller to be presented. You can fade, scale, rotate and manipulate the existing view and the new view however you like.
  5. “When the transition between the two view controllers begins, the existing view is added to a transition container view and the new view controller’s view is created but not yet visible, (means not added in container view automatically)

Now let’s embedded VC1 into navigation controller , Now this flow we called it Navigation Stack Animations

Figure 15

Before that let’s give some identifier to track easy , for VC1 → Main View → Red View (0,91,428,132) and have identifier VC1RedView

for VC2 → Main View → Red View (0,0,428,926) and have identifier VC2RedView

As shown inn Figure 17, when we embedded VC1 into navigation controller we are not getting what we want transitionContext.viewController(forKey: .from) return navigation controller , and po transitionContext.view(forKey: .from)?.subviews return navigationn view and transitionContext.view(forKey: .from)?.subviews[0] we expect VC1 redview but it showing wrong view. So the question is how can we solve it , now if you run the application it will not work beacuse when presenting we are using VC1.subviews[0].frame as stating value of animation and this value after adding navigation controller or container giving wrong value

Figure 17

Container View Controller

Container view controllers are a way to combine the content from multiple view controllers into a single user interface. Container view controllers are most often used to facilitate navigation and to create new user interface types based on existing content. Examples of container view controllers in UIKit include UINavigationController, UITabBarController, and UISplitViewController, all of which facilitate navigation between different parts of your user interface.

A UINavigationController object supports navigation through a hierarchical data set. A navigation interface presents one child view controller at a time. A navigation bar at the top of the interface displays the current position in the data hierarchy and displays a back button to move back one level. Navigation down into the data hierarchy is left to the child view controller and can involve the use of tables or buttons.Navigation between view controllers is managed jointly by the navigation controller and its children. When the user interacts with a button or table row of a child view controller, the child asks the navigation controller to push a new view controller into view. The child handles the configuration of the new view controller’s contents, but the navigation controller manages the transition animations. The navigation controller also manages the navigation bar, which displays a back button for dismissing the topmost view controller.

In short : but the navigation controller manages the transition animations so we will get the in .from container view ,

How we can solve it

Apple don’t provide this case solution

There is no ready-to-use API for your custom container UIViewController subclass that allows an arbitrary animation controller to automatically conduct the transition from one of your child view controllers to another, navigation controller embedded child controller modal animation , interactively or non-interactively. I am tempted to say it was not even Apple’s intention to support it. What is supported are the following transitions:

  • Navigation controller pushes and pops transition
  • Tab bar controller selection changes
  • Modal presentations and dismissals

Solution 1

As shown in Figure 18 , VC1 (Note it is main view of VC1 sorry for bad name), we extracted from navigation controller child view

Figure 18

Solution 2

As shown in Figure 19 , we directly pass the VC1redview

Figure 19

Next

In the next part we will see How we can customize push and pop animation of navigation controller

Useful links

--

--

Ali Akhtar

Senior iOS Engineer | HungerStation | Delivery Hero