Default Custom Transition for UIViewController

980 views Asked by At

I am developing a library and I want to provide a default custom transition between two view controllers, the user can also provide his own implementation the first idea that comes to my my mind is to override UIViewController and implement the UIViewControllerTransitioningDelegate and then users can subclass my CustomTransitionViewController is it the best way to do it ? any limitations ? is there a more elegant way using just protocols for example with default implementation ?

import UIKit

class CustomTransitionViewController: UIViewController, UIViewControllerTransitioningDelegate {

  required init?(coder aDecoder: NSCoder) {
      super.init(coder: aDecoder)
      self.transitioningDelegate = self
  }

  override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil:Bundle?)   {
      super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
      self.transitioningDelegate = self
  }

  func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
      return FadeInAnimator(transitionDuration: 0.5, startingAlpha: 0.8)
  }

  func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
      return FadeInAnimator(transitionDuration: 0.5, startingAlpha: 0.8)
  }
}
2

There are 2 answers

0
Mikk Rätsep On

You could also just make an extension for the UIViewController that does all the things you need and additionally conforms to your protocol, that enables the user to i.e. disable the default transitions.

As an example:

protocol ThorsHammer {
    var isDefaultTransitionEnabled: Bool { get set }
}

extension UIViewController: ThorsHammer {
    var isDefaultTransitionEnabled: Bool { /* this part sucks */ }

    // Check if the default transition should happen and
    // do some transition magic
}

How to add stored properties to extensions, you could have a look at this.

All of this, if you can find a way to set the transitioningDelegate somehow in the extension, otherwise you'll have to subclass.

0
pranjal On

I think one of the most elegant (and protocol oriented) ways to do this would be with a UIViewControllerTransitioningDelegate extension. Extend the protocol and provide a default implementation for animationController(forPresented: presenting: source:) and animationController(forDismissed:). The extension would look something like this:

extension UIViewControllerTransitioningDelegate where Self: UIViewController {
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return FadeInAnimator(transitionDuration: 0.5, startingAlpha: 0.8)
    }

    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return FadeInAnimator(transitionDuration: 0.5, startingAlpha: 0.8)
    }
}

And then tell your users to extend their view controllers to conform to this protocol. Since you already implemented the only requirements in a protocol extension, all they need to do is add the conformance declaration, like so:

class MyViewController: UIViewController, UIViewControllerTransitioningDelegate { }

If you want to extend all UIViewControllers to do so, you can do it with an empty extension:

extension UIViewController: UIViewControllerTransitioningDelegate { }

That should work. In my opinion, it's a very clean, elegant solution that doesn't require unnecessary subclassing. It also makes it easy to provide different implementations of animationController(forPresented: presenting: source:) and animationController(forDismissed:) in each view controller. Your users can just add their own implementations of the two aforementioned functions wherever they want. Also, you don't need to deal with messy Objective C stuff like associated objects.

If you're providing this functionality in some sort of prepackaged bundle and you want to hide the underlying implementation, you could declare your own protocol, make it a subprotocol of UIViewControllerTransitioningDelegate, extend it similarly, and have your users conform to your custom protocol instead of UIViewControllerTransitioningDelegate:

protocol MyProtocol: UIViewControllerTransitioningDelegate {
    //Add your own requirements (if you have any).
}

extension MyProtocol where Self: UIViewController {
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return FadeInAnimator(transitionDuration: 0.5, startingAlpha: 0.8)
    }

    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return FadeInAnimator(transitionDuration: 0.5, startingAlpha: 0.8)
    }

    //Satisfy your requirements (or let your users do it).
}

class MyViewController: UIViewController, MyProtocol { }

extension UIViewController: MyProtocol { }

Whichever route you take, this solution gives you what you want without making you deal with extraneous subclassing or ugly Objective C concepts. Good luck!