Custom Transition in Swift with present view controller - Non-interactive

Update: A fellow samaritan subscriber Sambaran Das took a different approach and found a bug in my approach. I have modified the article based on it.

Thanks Sambaran

I have seen many developers trying to achieve Custom transitions by adding child views instead of leveraging the API provided by Apple in iOS. I don’t think the ease of making custom transitions can be achieved by adding child views and animating them. I hope this article reaches the right people who need to change the way they understand transitions.

Custom transitions can be achieved in View Controllers getting presented or dismissed, Navigation Controllers Pushing or Popping, or even when a Tab Bar Controller switches its views. We do have a few built-in transitions which we can use. But indefinite ideas are evolving, and you might just need to make your own custom transition.

Let’s Get Started

Create an iOS App project and open the Main.storyboard file. Next, Drag and Drop a button in your view controller, give it the title “Present Second VC”. Next, add another View Controller in your storyboard, and drag-drop a button in it, give it the title “Dismiss View”. Tweak the background colors of Buttons and Views if you like. Your Views should look like something shown in the below image.

Setup Storyboard Preview.png

Create a new file of type Cocoa Touch Class, subclassing UIViewController, name it SecondViewController. Next, in the Main.storyboard file, select the new view controller you added, then in Identity Inspector, add the Class name and Storyboard Id as shown in the image below.

Identity Inspectior Preview.png

We are almost done with setting up the project. Create the IBAction for both the buttons in their respective classes, In ViewController.swift file presentClicked(_:), and dismissClicked(_:) in SecondViewController.swift file.

Defining Transitions

There are two types of transition required, one presenting a view, second dismissing it. We will be recreating the transition style of pushing views on navigation controller without a navigation controller. Once you finish reading this article, you’ll be able to code your ideas of transition with ease.

Let’s start by creating two subclasses of NSObject, name them PresentTransition and DismissTransition.

Open PresentTransition.swift file, and conform it to a protocol UIViewControllerAnimatedTransitioning, with this add a property

var animator: UIViewImplicitlyAnimating?

Now, how does it work? We need to define the duration of our transition, then define our animation, and then let it start. With the help of the UIViewControllerAnimatedTransitioning protocol, all this is executed in separate functions. Let’s see how it’s done.

In your PresentTransition.swift file, add the function given below, which defines the transition duration.

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return 0.3
}

Next, we will add a function that defines our transition, basically moving the SecondViewController right to left, and at the same time, moving out our current view. Once the transition is completed, we update the transitionContext the status of completion. We will need another function that will fetch this declared animation and start the animation.

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    let animator = self.interruptibleAnimator(using: transitionContext)
    animator.startAnimation()
}

func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
    if self.animator != nil {
        return self.animator!
    }

    let container = transitionContext.containerView
    let fromVC = transitionContext.viewController(forKey: .from)!

    let fromViewInitialFrame = transitionContext.initialFrame(for: fromVC)
    var fromViewFinalFrame = fromViewInitialFrame
    fromViewFinalFrame.origin.x = -fromViewFinalFrame.width

    let fromView = fromVC.view!
    let toView = transitionContext.view(forKey: .to)!

    var toViewInitialFrame = fromViewInitialFrame
    toViewInitialFrame.origin.x = toView.frame.size.width

    toView.frame = toViewInitialFrame
    container.addSubview(toView)

    let animator = UIViewPropertyAnimator(duration: self.transitionDuration(using: transitionContext), curve: .easeInOut) {

        toView.frame = fromViewInitialFrame
        fromView.frame = fromViewFinalFrame
    }

    animator.addCompletion { _ in
        transitionContext.completeTransition(true)
    }

    self.animator = animator

    return animator

}

Once our animation is completed, we will reset our animator property to nil in the protocol function animationEnded(_:).

func animationEnded(_ transitionCompleted: Bool) {
    self.animator = nil
}

It’s not over yet. We have defined our animation for Presenting the View Controller. Next, we need to declare the animation for Dismissing the View Controller.

Don’t worry, just copy all the code we declared from PresentTransition.swift to DismissTransition.swift, including the global property animator. Next, replace the function interruptibleAnimator(using:) -> UIViewImplicitlyAnimating with the code below.

func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
    if self.animator != nil {
        return self.animator!
    }
    
    let fromVC = transitionContext.viewController(forKey: .from)!
    
    var fromViewInitialFrame = transitionContext.initialFrame(for: fromVC)
    fromViewInitialFrame.origin.x = 0
    var fromViewFinalFrame = fromViewInitialFrame
    fromViewFinalFrame.origin.x = fromViewFinalFrame.width
    
    let fromView = fromVC.view!
    let toView = transitionContext.viewController(forKey: .to)!.view!
    
    var toViewInitialFrame = fromViewInitialFrame
    toViewInitialFrame.origin.x = -toView.frame.size.width
    
    toView.frame = toViewInitialFrame
    
    let animator = UIViewPropertyAnimator(duration: self.transitionDuration(using: transitionContext), curve: .easeInOut) {
        
        toView.frame = fromViewInitialFrame
        fromView.frame = fromViewFinalFrame
    }
    
    animator.addCompletion { _ in
        transitionContext.completeTransition(true)
    }
    
    self.animator = animator
    
    return animator
    
}

There are only a few things that changed here. It’s mostly about the x co-ordinate.

With this, we have declared the style of our transition. Now, let’s just complete the final steps.

Almost there!

Open the ViewController.swift file, and in the presentClicked(_:) function you created, add the lines given below to initiate the transition.

let secondVC = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
secondVC.modalPresentationStyle = .custom
secondVC.transitioningDelegate = self

self.present(secondVC, animated: true, completion: nil)

As you can see, we have declared transitioningDelegate as self. So we’ll have to conform to its protocol to make our Custom Transitions work.

extension ViewController: UIViewControllerTransitioningDelegate {

    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return PresentTransition()
    }

    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return DismissTransition()
    }
}

The above two functions defined in the protocol help us return them the type of transitions we are going to use.

There’s one last thing to do, Open your SecondViewController.swift file, and in the dismissClicked(_:) action you created, add the line mentioned below to dismiss the view.

self.dismiss(animated: true, completion: nil)

Run.

The smooth transition of navigation is achieved without the UINavigationController. There’s so much more you can do with Custom Transitions.

What’s Next?

As I said, There’s so much more you can do with Custom Transitions. In the next few articles, I’ll be posting tutorials about some of the most useful Custom Transitions. So if you haven’t subscribed to my article yet, don’t wait. Let the transition begin.

I hope you guys liked the article. Please subscribe to keep me motivated 😉 and to receive weekly updates. I’ll be posting my articles every Tuesday or Wednesday. That will be the day you’ll have to open your Inbox for me. Also, feel free to share your experience with me on Twitter, maybe follow me there 🤗.

Download the final project here.

Adding Gradient Colors to your View (UIView, UIButton, UILabel, etc.) - UIKit

Building Peek and Pop with UICollectionView ...and UITableView