I will show with an example:
| import UIKit | |
| class PresentingViewController: UIViewController { | |
| override func viewDidLoad() { | |
| super.viewDidLoad() | |
| self.view.backgroundColor = .systemBlue | |
| let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(presentTapped)) | |
| self.view.addGestureRecognizer(tapGestureRecognizer) | |
| } | |
| @IBAction func presentTapped() { | |
| let presented = PresentedViewController() | |
| self.present(presented, animated: true, completion: nil) | |
| } | |
| } | |
| class PresentedViewController: UIViewController { | |
| override func viewDidLoad() { | |
| super.viewDidLoad() | |
| self.view.backgroundColor = .systemGreen | |
| let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissTapped)) | |
| self.view.addGestureRecognizer(tapGestureRecognizer) | |
| } | |
| @objc func dismissTapped() { | |
| self.dismiss(animated: true, completion: nil) | |
| } | |
| } |
I have two UIViewControllers, one is to present and the other one is to be presented. They both have UITapGestureRecognizer on their views. When tapped, the presenting one presesents and the presented one dismisses. And the transition is not custom yet.
To make it custom, first we need an animator class which conforms to UIViewControllerAnimatedTransitioning protocol. In this class, we set the duration and the animation of the transition:
| import UIKit | |
| class MoveVerticallyAnimator: NSObject, UIViewControllerAnimatedTransitioning { | |
| let duration = 2.0 | |
| var presenting = true | |
| func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { | |
| return duration | |
| } | |
| func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { | |
| let containerView = transitionContext.containerView | |
| let fromView = transitionContext.view(forKey: .from)! | |
| let toView = transitionContext.view(forKey: .to)! | |
| let screenHeight = UIScreen.main.bounds.height | |
| let screenWidth = UIScreen.main.bounds.width | |
| toView.frame = CGRect(x: 0, y: presenting ? -screenHeight : screenHeight, width: screenWidth, height: screenHeight) | |
| containerView.addSubview(toView) | |
| UIView.animate(withDuration: duration, | |
| delay: 0.0, | |
| usingSpringWithDamping: 0.6, | |
| initialSpringVelocity: 0.0, | |
| options: []) { | |
| toView.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight) | |
| fromView.frame = CGRect(x: 0, y: self.presenting ? screenHeight + 20 : -screenHeight – 20, width: screenWidth, height: screenHeight) | |
| } completion: { _ in | |
| transitionContext.completeTransition(true) | |
| } | |
| } | |
| } |
Now that we have our transitioning animator ready, we need to make the presenting view controller conform to UIViewControllerTransitioningDelegate and return the animator in its delegate methods. Also while presenting, we need to set transitioningDelegate and modalPresentationStyle:
| import UIKit | |
| class PresentingViewController: UIViewController { | |
| let transition = MoveVerticallyAnimator() | |
| override func viewDidLoad() { | |
| super.viewDidLoad() | |
| self.view.backgroundColor = .systemBlue | |
| let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(presentTapped)) | |
| self.view.addGestureRecognizer(tapGestureRecognizer) | |
| } | |
| @IBAction func presentTapped() { | |
| let presented = PresentedViewController() | |
| presented.transitioningDelegate = self | |
| presented.modalPresentationStyle = .fullScreen | |
| self.present(presented, animated: true, completion: nil) | |
| } | |
| } | |
| extension PresentingViewController: UIViewControllerTransitioningDelegate { | |
| func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { | |
| transition.presenting = true | |
| return transition | |
| } | |
| func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { | |
| transition.presenting = false | |
| return transition | |
| } | |
| } |
And the final transition:
