Alright, so here's what I had to do in addition to the UINavigationController
subclass in my question. I had to make use of UIViewControllerAnimatedTransitioning
delegate in order to achieve what I wanted.
I created two class that subclassed NSObject
and conformed to the above protocol.
First one is for present
transition-
import UIKit
class ComposePresentTransitionController: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.6
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromViewController = transitionContext.viewController(forKey: .from)!
let toViewController = transitionContext.viewController(forKey: .to)!
let containerView = transitionContext.containerView
let screenBounds = UIScreen.main.bounds
let topOffset: CGFloat = UIApplication.shared.statusBarFrame.height
var finalFrame = transitionContext.finalFrame(for: toViewController)
finalFrame.origin.y += topOffset
finalFrame.size.height -= topOffset
toViewController.view.frame = CGRect(x: 0.0, y: screenBounds.size.height,
width: finalFrame.size.width,
height: finalFrame.size.height)
containerView.addSubview(toViewController.view)
UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 1.0, options: .curveEaseOut, animations: {
toViewController.view.frame = finalFrame
fromViewController.view.alpha = 0.3
}) { (finished) in
transitionContext.completeTransition(finished)
}
UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, options: .curveEaseOut,
animations: {
}) { (finished) in
}
}
}
Second for dismiss
transition-
import UIKit
class ComposeDismissTransitionController: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromViewController = transitionContext.viewController(forKey: .from)!
let toViewController = transitionContext.viewController(forKey: .to)!
let screenBounds = UIScreen.main.bounds
var finalFrame = fromViewController.view.frame
finalFrame.origin.y = screenBounds.size.height
UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, options: .curveEaseIn,
animations: {
fromViewController.view.frame = finalFrame
toViewController.view.alpha = 1.0
}) { (finished) in
fromViewController.view.removeFromSuperview()
transitionContext.completeTransition(finished)
}
}
}
Then, from the view controller that's going to present this viewcontroller needed to conform to the UIViewControllerTransitioningDelegate
protocol and implement the animationController:forPresented
and animationController:forDismissed
methods like so-
extension PresentingVC: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController,
presenting: UIViewController,
source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return ComposePresentTransitionController()
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return ComposeDismissTransitionController()
}
}
Once that was done, the PresentingVC
needed to set the UIViewControllerTransitioningDelegate
to self
which was done in the prepareForSegue
-
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
segue.destination.transitioningDelegate = self
}
If you are a segue hater like me, then you can do this from an IBAction
of a button all in the code too.
@IBAction func composeTap(_ sender: Any) {
let composeVC = self.storyboard?.instantiateViewController(withIdentifier: "compose") as! ComposeNavigController //this should be the navigation controller itself not the embedded viewcontroller
composeVC.transitioningDelegate = self
self.present(composeVC, animated: true, completion: nil)
}
Thats it... now whenever the button was tapped to present the Compose screen modally, it would modally animate with the effect I desired. I added a little bit of bouncing effect just for fun :)
