0

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!

Luke Redmore
  • 449
  • 3
  • 10

1 Answers1

0

A couple of observations:

  1. 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.

  2. 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 all init 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
        }
    
        ...
    }
    
  3. 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.

Rob
  • 415,655
  • 72
  • 787
  • 1,044