Present modal view controller in half size parent controller

怎甘沉沦 提交于 2019-11-26 12:02:04

问题


I am trying to present modal view controller on other viewcontroller sized to half parent view controller. But it always present in full screen view.

I have created freeform sized View controller in my storyboard with fixed frame size. 320 X 250.

var storyboard = UIStoryboard(name: \"Main\", bundle: nil)
var pvc = storyboard.instantiateViewControllerWithIdentifier(\"CustomTableViewController\") as ProductsTableViewController
self.presentViewController(pvc, animated: true, completion: nil)

I have tried to set frame.superview and it doesn\'t help.

\"Picture

Please advice.


回答1:


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 presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController!, sourceViewController source: UIViewController) -> UIPresentationController? {
    return HalfSizePresentationController(presentedViewController: presented, presentingViewController: presenting)
} 

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

pvc.modalPresentationStyle = UIModalPresentationStyle.Custom
pvc.transitioningDelegate = self

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

class HalfSizePresentationController : UIPresentationController {
    override func frameOfPresentedViewInContainerView() -> CGRect {
        return CGRect(x: 0, y: containerView.bounds.height/2, width: containerView.bounds.width, height: containerView.bounds.height/2)
    }
}

Here is the working code in its entirety:

class ViewController: UIViewController, UIViewControllerTransitioningDelegate {

    @IBAction func tap(sender: AnyObject) {
        var storyboard = UIStoryboard(name: "Main", bundle: nil)
        var pvc = storyboard.instantiateViewControllerWithIdentifier("CustomTableViewController") as UITableViewController

        pvc.modalPresentationStyle = UIModalPresentationStyle.Custom
        pvc.transitioningDelegate = self
        pvc.view.backgroundColor = UIColor.redColor()

        self.presentViewController(pvc, animated: true, completion: nil)
    }

    func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController!, sourceViewController source: UIViewController) -> UIPresentationController? {
        return HalfSizePresentationController(presentedViewController: presented, presentingViewController: presentingViewController)
    }
}

class HalfSizePresentationController : UIPresentationController {
    override func frameOfPresentedViewInContainerView() -> CGRect {
        return CGRect(x: 0, y: containerView.bounds.height/2, width: containerView.bounds.width, height: containerView.bounds.height/2)
    }
}



回答2:


It would be a a clean architect if you push some delegate method of UIViewControllerTransitioningDelegate in your ViewController that want to be present 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 UIKit

final 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 / 2
var isPresenting = false

init() {
    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)
}

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:

All code is published at my Github




回答3:


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!




回答4:


Jannis captured the overall strategy well. It didn't work for me in iOS 9.x with swift 3. On the presenting VC, the action to launch the presented VC is similar to what was presented above with some very minor changes as below:

let storyboard = UIStoryboard(name: "Main", bundle: nil)
let pvc = storyboard.instantiateViewController(withIdentifier: "SomeScreen") as SomeViewController

pvc.modalPresentationStyle = .custom
pvc.transitioningDelegate = self

present(pvc, animated: true, completion: nil)

To implement UIViewControllerTransitioningDelegate on the same presenting VC, the syntax is quite different as highlighted in SO answer in https://stackoverflow.com/a/39513247/2886158. This is was the most tricky part for me. Here is the protocol implementation:

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

For the UIPresentationController class, I had to override the variable frameOfPresentedViewInContainerView, not method, as below:

class HalfSizePresentationController: UIPresentationController {
    override var frameOfPresentedViewInContainerView: CGRect {
        return CGRect(x: 0, y: 0, width: containerView!.bounds.width, height: containerView!.bounds.height/2)
    }
}

There were some questions about how to dismiss the view after presentation. You can implement all the usual logic on your presented VC like any other VC. I implementation an action to dismiss the view in SomeViewController when a user tabs outside the presented VC.




回答5:


To add to Jannis' answer:

In case your pop-view is a UIViewController to which you ADD a Table on load/setup, you will need to ensure that the table frame you create matches the desired width of the actual view.

For example:

let tableFrame: CGRect = CGRectMake(0, 0, chosenWidth, CGFloat(numOfRows) * rowHeight)

where chosenWidth is the width you set in your custom class (in the above: containerView.bounds.width)

You do not need to enforce anything on the Cell itself as the table container (at least in theory) should force the cell to the right width.




回答6:


I use below logic to present half screen ViewController

 let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let expVC = storyboard.instantiateViewController(withIdentifier: "AddExperinceVC") as! AddExperinceVC
    expVC.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext

    self.present(expVC, animated: true, completion: nil)


来源:https://stackoverflow.com/questions/29219688/present-modal-view-controller-in-half-size-parent-controller

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!