Существующий контроллер модального представления в половинном родительском контроллере

Я пытаюсь представить modal view controller на другом контроллере viewcontroller размером до половины родительского контроллера. Но он всегда присутствует в полноэкранном режиме.

Я создал контроллер размера свободной формы размером в моей раскадровке с фиксированным размером кадра. 320 X 250.

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

Я попытался установить frame.superview, и это не помогает.

Picture example

Пожалуйста, совет.

Ответы

Ответ 1

Для достижения этой цели вы можете использовать UIPresentationController.

Для этого вы даете представление ViewController реализовать UIViewControllerTransitioningDelegate и верните PresentationController для представления с половинным размером:

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

При представлении стиля презентации установите .Custom и установите свой делегат перехода:

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

Контроллер презентации возвращает только кадр для вашего представленного контроллера представления:

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

Вот рабочий код целиком:

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: 0, width: containerView.bounds.width, height: containerView.bounds.height/2)
    }
}

Ответ 2

Было бы чистым архитектором, если вы UIViewControllerTransitioningDelegate какой-нибудь метод делегата UIViewControllerTransitioningDelegate в свой ViewController, который хочет быть полумодальным.

Предполагая, что у нас есть ViewControllerA присутствует ViewControllerB с половиной модального ViewControllerB.

в ViewControllerA просто представить ViewControllerB с пользовательским modalPresentationStyle

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

И в 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)
        })
    }
}
}

Результат:

enter image description here

Весь код опубликован на моем Github

Ответ 3

Джаннис хорошо поймал общую стратегию. Это не сработало для меня в iOS 9.x с быстрым 3. На представленном VC действие для запуска представленного VC похоже на то, что было представлено выше с некоторыми очень незначительными изменениями, как показано ниже:

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)

Чтобы реализовать UIViewControllerTransitioningDelegate в том же представленном VC, синтаксис совсем другой, как указано в ответе SO в fooobar.com/questions/332781/.... Это была самая сложная часть для меня. Вот реализация протокола:

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

Для класса UIPresentationController мне пришлось переопределить переменную frameOfPresentedViewInContainerView, а не метод, как показано ниже:

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

Были некоторые вопросы о том, как отклонить представление после презентации. Вы можете реализовать всю обычную логику на своем представленном VC, как и любой другой VC. Я реализую действие, чтобы отклонить представление в SomeViewController, когда пользователь заносит вкладку вне представленного VC.

Ответ 4

На всякий случай, если кто-то хочет сделать это со Swift 4, как я.

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)
    }
}

Где класс HalfSizePresentationController состоит из:

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)
        }
    }
}

Ура!

Ответ 5

Чтобы добавить ответ Jannis:

В случае, если ваш pop-view является UIViewController, к которому вы добавили таблицу для загрузки/установки, вам необходимо убедиться, что созданный вами столбец соответствует желаемой ширине фактического представления.

Например:

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

где selectedWidth - это ширина, которую вы установили в своем пользовательском классе (в приведенном выше примере: containerView.bounds.width)

Вам не нужно принудительно вводить в действие что-либо в самой ячейке, поскольку контейнер таблицы (по крайней мере теоретически) должен заставить ячейку иметь правую ширину.

Ответ 6

Я использую ниже логику, чтобы представить полуэкранный 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)