Following this answer, I managed to add half-modally presented ViewControllerB
to ViewControllerA
. However, in the example, ViewControllerA
is just a red view. I would like to connect it to IB in order to customize it. I tried creating a view controller in IB of class ViewControllerB
and connect one of its views to @IBOutlet var menuView: UIView!
instead of creating menuView
programmatically (as in the example). However, nil
was found when adding menuView
to the view as a subview. Any help would be great. Thanks!

- 449
- 3
- 10
1 Answers
A couple of observations:
In that example, there is a line that says:
let vc = ViewControllerB()
You could define this view controller’s view in a NIB and this would work fine. But if you’re using storyboards, replace that with ...
let vc = storyboard!.instantiateViewController(withIdentifier: “identifier")
... where
identifier
is some string that you supplied as the “Storyboard ID” in the “Identity inspector” in IB for that scene.If this is being triggered by a button or something like that, you can also remove this
gotoVCB
in that sample, and just do the modal presentation right in IB like usual (e.g. control-dragging from the button to the next scene) and choosing “Present modally”. But you’d have to make sure that you configured allinit
methods, e.g.:class ViewControllerB: UIViewController { // used if you use NIBs or just call `init()` override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) configure() } // used if you use storyboards required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) configure() } private func configure() { modalPresentationStyle = .custom transitioningDelegate = self } ... }
Unrelated to the immediate question, I might suggest splitting up that answer’s code into separate objects to prevent view controller “bloat”, with better separation of responsibilities between a transitioning delegate object, the animation controller, etc.
For example, maybe:
class ViewControllerB: UIViewController { let customTransitioningDelegate = PopUpMenuTransitioningDelegate() override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) configure() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) configure() } func configure() { modalPresentationStyle = .custom transitioningDelegate = customTransitioningDelegate } }
And
class PopUpMenuTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate { func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return PopUpMenuAnimationController(transitionType: .presenting) } func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return PopUpMenuAnimationController(transitionType: .dismissing) } }
And
class PopUpMenuAnimationController: NSObject, UIViewControllerAnimatedTransitioning { enum TransitionType { case presenting case dismissing } let transitionType: TransitionType init(transitionType: TransitionType) { self.transitionType = transitionType super.init() } func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 1 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { let containerView = transitionContext.containerView guard let toVC = transitionContext.viewController(forKey: .to), let fromVC = transitionContext.viewController(forKey: .from) else { return } switch transitionType { case .presenting: containerView.addSubview(toVC.view) var rect = fromVC.view.bounds let menuHeight = fromVC.view.bounds.height / 2 rect.origin.y += fromVC.view.bounds.height rect.size.height = menuHeight toVC.view.frame = rect toVC.view.alpha = 0 UIView.animate(withDuration: 0.4, delay: 0, options: [.curveEaseOut], animations: { rect.origin.y = menuHeight toVC.view.frame = rect toVC.view.alpha = 1 }, completion: { _ in transitionContext.completeTransition(!transitionContext.transitionWasCancelled) }) case .dismissing: UIView.animate(withDuration: 0.4, delay: 0, options: [.curveEaseOut], animations: { var rect = fromVC.view.frame rect.origin.y = toVC.view.bounds.height fromVC.view.frame = rect fromVC.view.alpha = 0 }, completion: { _ in transitionContext.completeTransition(!transitionContext.transitionWasCancelled) }) } } }
That decouples the transitioning delegate and animation controller from the view controller and lets you use this with other view controllers should you ever need to.
Note, I’m also a little more careful about the parameter to
completeTransition
, too. Once you start playing around with interactive transitions, you might support cancelable transitions.

- 415,655
- 72
- 787
- 1,044