Present modal view controller in half size parent controller Present modal view controller in half size parent controller ios ios

Present modal view controller in half size parent controller


You can use a UIPresentationController to achieve this.

For this you let the presenting ViewController implement the UIViewControllerTransitioningDelegate and return your PresentationController for the half sized presentation:

func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {    return HalfSizePresentationController(presentedViewController: presented, presenting: presentingViewController)}

When presenting you set the presentation style to .Custom and set your transitioning delegate:

pvc.modalPresentationStyle = .custompvc.transitioningDelegate = self

The presentation controller only returns the frame for your presented view controller:

class HalfSizePresentationController: UIPresentationController {    override var frameOfPresentedViewInContainerView: CGRect {        guard let bounds = containerView?.bounds else { return .zero }        return CGRect(x: 0, y: bounds.height / 2, width: bounds.width, height: bounds.height / 2)    }}

Here is the working code in its entirety:

class ViewController: UIViewController, UIViewControllerTransitioningDelegate {    @IBAction func tap(sender: AnyObject) {        let storyboard = UIStoryboard(name: "Main", bundle: nil)        let pvc = storyboard.instantiateViewController(withIdentifier: "CustomTableViewController") as! UITableViewController        pvc.modalPresentationStyle = .custom        pvc.transitioningDelegate = self        pvc.view.backgroundColor = .red        present(pvc, animated: true)    }        func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {        return HalfSizePresentationController(presentedViewController: presented, presenting: presentingViewController)    }}class HalfSizePresentationController: UIPresentationController {    override var frameOfPresentedViewInContainerView: CGRect {        guard let bounds = containerView?.bounds else { return .zero }        return CGRect(x: 0, y: bounds.height / 2, width: bounds.width, height: bounds.height / 2)    }}


It would be a a clean architect if you push some delegate methods of UIViewControllerTransitioningDelegate in your ViewController that want to be presented as half modal.

Assuming we have ViewControllerA present ViewControllerB with half modal.

in ViewControllerA just present ViewControllerB with custom modalPresentationStyle

func gotoVCB(_ sender: UIButton) {    let vc = ViewControllerB()    vc.modalPresentationStyle = .custom    present(vc, animated: true, completion: nil)}

And in ViewControllerB:

import UIKitfinal class ViewControllerB: UIViewController {lazy var backdropView: UIView = {    let bdView = UIView(frame: self.view.bounds)    bdView.backgroundColor = UIColor.black.withAlphaComponent(0.5)    return bdView}()let menuView = UIView()let menuHeight = UIScreen.main.bounds.height / 2var isPresenting = falseinit() {    super.init(nibName: nil, bundle: nil)    modalPresentationStyle = .custom    transitioningDelegate = self}required init?(coder aDecoder: NSCoder) {    fatalError("init(coder:) has not been implemented")}override func viewDidLoad() {    super.viewDidLoad()        view.backgroundColor = .clear    view.addSubview(backdropView)    view.addSubview(menuView)        menuView.backgroundColor = .red    menuView.translatesAutoresizingMaskIntoConstraints = false    menuView.heightAnchor.constraint(equalToConstant: menuHeight).isActive = true    menuView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true    menuView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true    menuView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(ViewControllerB.handleTap(_:)))    backdropView.addGestureRecognizer(tapGesture)}@objc func handleTap(_ sender: UITapGestureRecognizer) {    dismiss(animated: true, completion: nil)}}extension ViewControllerB: UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning {func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {    return self}func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {    return self}func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {    return 1}func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {    let containerView = transitionContext.containerView    let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)    guard let toVC = toViewController else { return }    isPresenting = !isPresenting        if isPresenting == true {        containerView.addSubview(toVC.view)                menuView.frame.origin.y += menuHeight        backdropView.alpha = 0                UIView.animate(withDuration: 0.4, delay: 0, options: [.curveEaseOut], animations: {            self.menuView.frame.origin.y -= self.menuHeight            self.backdropView.alpha = 1        }, completion: { (finished) in            transitionContext.completeTransition(true)        })    } else {        UIView.animate(withDuration: 0.4, delay: 0, options: [.curveEaseOut], animations: {            self.menuView.frame.origin.y += self.menuHeight            self.backdropView.alpha = 0        }, completion: { (finished) in            transitionContext.completeTransition(true)        })    }}}

The result:

enter image description here

All code is published at my Github


Just in case someone is looking to do this with Swift 4, as I was.

class MyViewController : UIViewController {    ...    @IBAction func dictionaryButtonTouchUp(_ sender: UIButton) {        let modalViewController = ...        modalViewController.transitioningDelegate = self        modalViewController.modalPresentationStyle = .custom        self.present(modalViewController, animated: true, completion: nil)    }}extension MyViewController : UIViewControllerTransitioningDelegate {    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {        return HalfSizePresentationController(presentedViewController: presented, presenting: presenting)    }}

Where the HalfSizePresentationController class is composed of:

class HalfSizePresentationController : UIPresentationController {    override var frameOfPresentedViewInContainerView: CGRect {        get {            guard let theView = containerView else {                return CGRect.zero            }            return CGRect(x: 0, y: theView.bounds.height/2, width: theView.bounds.width, height: theView.bounds.height/2)        }    }}

Cheers!