ios - open - swift present modal view controller programmatically
Presentar el controlador de vista modal en el controlador principal de tamaƱo medio (6)
Estoy tratando de presentar el controlador de vista modal en otro controlador de vista del tamaño del controlador de vista medio primario. Pero siempre presente en vista de pantalla completa.
He creado un controlador View de tamaño libre en mi guión gráfico con un tamaño de marco fijo. 320 x 250.
var storyboard = UIStoryboard(name: "Main", bundle: nil)
var pvc = storyboard.instantiateViewControllerWithIdentifier("CustomTableViewController") as ProductsTableViewController
self.presentViewController(pvc, animated: true, completion: nil)
He intentado configurar frame.superview y no ayuda.
Por favor aconséjame.
Jannis capturó bien la estrategia general. No funcionó para mí en iOS 9.x con swift 3. En el VC actual, la acción para lanzar el VC presentado es similar a lo que se presentó anteriormente con algunos cambios menores como los siguientes:
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)
Para implementar UIViewControllerTransitioningDelegate
en el mismo VC de presentación, la sintaxis es bastante diferente como se destaca en la respuesta SO en https://.com/a/39513247/2886158 . Esta fue la parte más difícil para mí. Aquí está la implementación del protocolo:
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return HalfSizePresentationController(presentedViewController:presented, presenting: presenting)
}
Para la clase UIPresentationController
, tuve que anular la variable frameOfPresentedViewInContainerView
, no el método, como se muestra a continuación:
class HalfSizePresentationController: UIPresentationController {
override var frameOfPresentedViewInContainerView: CGRect {
return CGRect(x: 0, y: 0, width: containerView!.bounds.width, height: containerView!.bounds.height/2)
}
}
Hubo algunas preguntas sobre cómo descartar la vista después de la presentación. Puede implementar toda la lógica habitual en su VC presentada como cualquier otro VC. Implemento una acción para descartar la vista en SomeViewController
cuando un usuario SomeViewController
pestañas fuera del VC presentado.
Para agregar a la respuesta de Jannis:
En caso de que su vista emergente sea un UIViewController al que AGREGA una tabla en la carga / configuración, deberá asegurarse de que el marco de la tabla que cree coincida con el ancho deseado de la vista real.
Por ejemplo:
let tableFrame: CGRect = CGRectMake(0, 0, chosenWidth, CGFloat(numOfRows) * rowHeight)
donde selectedWidth es el ancho que estableció en su clase personalizada (en lo anterior: containerView.bounds.width )
No es necesario imponer nada en la celda, ya que el contenedor de la tabla (al menos en teoría) debe forzar la celda al ancho correcto.
Puede utilizar un UIPresentationController
para lograr esto.
Para esto, permite que ViewController
presente implemente UIViewControllerTransitioningDelegate
y devuelva su PresentationController
para la presentación de tamaño medio:
func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController!, sourceViewController source: UIViewController) -> UIPresentationController? {
return HalfSizePresentationController(presentedViewController: presented, presentingViewController: presenting)
}
Al presentar, configura el estilo de presentación en .Custom
y configure su delegado de transición:
pvc.modalPresentationStyle = UIModalPresentationStyle.Custom
pvc.transitioningDelegate = self
El controlador de presentación solo devuelve el marco para el controlador de vista presentado:
class HalfSizePresentationController : UIPresentationController {
override func frameOfPresentedViewInContainerView() -> CGRect {
return CGRect(x: 0, y: 0, width: containerView.bounds.width, height: containerView.bounds.height/2)
}
}
Aquí está el código de trabajo en su totalidad:
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)
}
}
Sería un arquitecto limpio si presiona algún método delegado de UIViewControllerTransitioningDelegate
en su ViewController que desea estar presente en la mitad modal.
Suponiendo que tengamos ViewControllerA
presentamos ViewControllerB
con medio modal.
en ViewControllerA
solo presente ViewControllerB
con modalPresentationStyle personalizado
func gotoVCB(_ sender: UIButton) {
let vc = ViewControllerB()
vc.modalPresentationStyle = .custom
present(vc, animated: true, completion: nil)
}
Y en 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)
})
}
}
}
El resultado:
Todo el código se publica en mi Github
Solo en caso de que alguien esté buscando hacer esto con Swift 4, como lo estaba yo.
class MyViewController : UIViewController {
...
@IBAction func dictionaryButtonTouchUp(_ sender: UIButton) {
// Show Apple''s dictionary view.
let referenceViewController = UIReferenceLibraryViewController(term: self.textView.currentWord)
referenceViewController.view.tintColor = colorLiteral(red: 0.3098039216, green: 0.6745098039, blue: 0.9176470588, alpha: 1)
referenceViewController.transitioningDelegate = self
referenceViewController.modalPresentationStyle = .custom
self.present(referenceViewController, animated: true, completion: nil)
}
}
extension MyViewController : UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return HalfSizePresentationController(presentedViewController: presented, presenting: presenting)
}
}
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)
}
}
}
¡Aclamaciones!
Utilizo la lógica de abajo para presentar media pantalla 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)