web-dev-qa-db-fra.com

Présenter le contrôleur de vue modale dans le contrôleur parent demi-taille

J'essaie de présenter le contrôleur de vue modale sur un autre contrôleur de taille de la moitié du contrôleur de vue parent. Mais il est toujours présent en mode plein écran.

J'ai créé un contrôleur View de taille libre dans mon storyboard avec une taille d'image fixe. 320 X 250.

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

J'ai essayé de définir frame.superview et cela n'aide pas.

Picture example

Veuillez conseiller.

29
Anton

Vous pouvez utiliser un UIPresentationController pour y parvenir.

Pour cela, vous laissez le présentateur ViewController implémenter le UIViewControllerTransitioningDelegate et renvoyez votre PresentationController pour la présentation de demi-taille:

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

Lors de la présentation, vous définissez le style de présentation sur .Custom et définissez votre délégué de transition:

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

Le contrôleur de présentation renvoie uniquement le cadre de votre contrôleur de vue présenté:

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

Voici le code de travail dans son intégralité:

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

Ce serait un architecte propre si vous Poussez une méthode déléguée de UIViewControllerTransitioningDelegate dans votre ViewController qui veut être présent à moitié modal.

En supposant que nous avons ViewControllerA présent ViewControllerB avec demi modal.

dans ViewControllerA juste présent ViewControllerB avec modalPresentationStyle personnalisé

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

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

Le résultat:

enter image description here

Tout le code est publié sur mon Github

26
Khuong

Juste au cas où quelqu'un chercherait à le faire avec Swift 4, comme je l'étais.

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

Où la classe HalfSizePresentationController est composée de:

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

À votre santé!

15
Francois Nadeau

Jannis a bien saisi la stratégie globale. Cela n'a pas fonctionné pour moi dans iOS 9.x avec Swift 3. Sur le VC de présentation, l'action pour lancer le VC VC présenté est similaire à ce que a été présenté ci-dessus avec quelques changements très mineurs comme ci-dessous:

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)

Pour implémenter UIViewControllerTransitioningDelegate sur le même VC de présentation, la syntaxe est très différente, comme indiqué dans SO réponse dans https://stackoverflow.com/a/39513247/ 2886158 . C'était la partie la plus délicate pour moi. Voici l'implémentation du protocole:

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

Pour la classe UIPresentationController, j'ai dû remplacer la variable frameOfPresentedViewInContainerView, pas la méthode, comme ci-dessous:

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

Il y avait des questions sur la façon de rejeter la vue après la présentation. Vous pouvez implémenter toute la logique habituelle sur votre VC VC présenté comme tout autre VC. J'implémente une action pour fermer la vue dans SomeViewController lorsqu'un utilisateur tabule en dehors du VC présenté.

6
Heelara

Pour ajouter à la réponse de Jannis:

Dans le cas où votre pop-view est un UIViewController auquel vous ajoutez une table lors du chargement/de la configuration, vous devrez vous assurer que le cadre de table que vous créez correspond à la largeur souhaitée de la vue réelle.

Par exemple:

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

selectedWidth est la largeur que vous définissez dans votre classe personnalisée (dans ce qui précède: containerView.bounds.width)

Vous n'avez pas besoin d'imposer quoi que ce soit sur la cellule elle-même car le conteneur de table (au moins en théorie) devrait forcer la cellule à la bonne largeur.

1
goggelj

J'utilise la logique ci-dessous pour présenter le demi-écran 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)
1
Josh Gray