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.
Veuillez conseiller.
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)
}
}
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:
Tout le code est publié sur mon Github
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é!
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é.
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)
où 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.
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)