Ver controlleres modal: cómo mostrar y descartar

Estoy rompiendo la cabeza durante la última semana sobre cómo resolver el problema con mostrar y descartar varios controlleres de vista. He creado un proyecto de ejemplo y he pegado el código directamente desde el proyecto. Tengo 3 controlleres de vista con sus correspondientes files .xib. MainViewController, VC1 y VC2. Tengo dos botones en el controller de vista principal.

- (IBAction)VC1Pressed:(UIButton *)sender { VC1 *vc1 = [[VC1 alloc] initWithNibName:@"VC1" bundle:nil]; [vc1 setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal]; [self presentViewController:vc1 animated:YES completion:nil]; } 

Esto abre VC1 sin problemas. En VC1, tengo otro button que debería abrir VC2 mientras que al mismo time descarte VC1.

 - (IBAction)buttonPressedFromVC1:(UIButton *)sender { VC2 *vc2 = [[VC2 alloc] initWithNibName:@"VC2" bundle:nil]; [vc2 setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal]; [self presentViewController:vc2 animated:YES completion:nil]; [self dismissViewControllerAnimated:YES completion:nil]; } // This shows a warning: Attempt to dismiss from view controller <VC1: 0x715e460> while a presentation or dismiss is in progress! - (IBAction)buttonPressedFromVC2:(UIButton *)sender { [self dismissViewControllerAnimated:YES completion:nil]; } // This is going back to VC1. 

Quiero que regrese al controller de vista principal mientras que al mismo time VC1 debería haberse eliminado de la memory para siempre. VC1 solo debería aparecer cuando hago clic en el button VC1 en el controller principal.

El otro button en el controller de vista principal también debería ser capaz de mostrar VC2 directamente evitando VC1 y debería volver al controller principal cuando se hace clic en un button en VC2. No hay códigos largos, loops o timeres. Solo llamadas al hueso para ver los controlleres.

Esta línea:

 [self dismissViewControllerAnimated:YES completion:nil]; 

no está enviando un post a sí mismo, en realidad está enviando un post a su VC de presentación, pidiéndole que haga el desestimado. Cuando presenta un VC, crea una relación entre el VC que presenta y el presentado. Por lo tanto, no debe destruir el VC de presentación mientras se presenta (el VC presentado no puede enviar ese post de rechazo …). Como no estás realmente teniendo en count esto, estás dejando la aplicación en un estado confuso. Ver mi respuesta Despedir un controller de vista presentado en el que recomiendo este método está más claramente escrito:

 [self.presentingViewController dismissViewControllerAnimated:YES completion:nil]; 

En su caso, debe asegurarse de que todo el control se realice en mainVC . Debe usar un delegado para enviar el post correcto a MainViewController desde ViewController1, de modo que mainVC pueda descartar VC1 y luego presentar VC2.

En VC2 VC1, agregue un protocolo en su file .h por encima de @interface:

 @protocol ViewController1Protocol <NSObject> - (void)dismissAndPresentVC2; @end 

y más abajo en el mismo file en la sección @interface declara una propiedad para contener el puntero de delegado:

 @property (nonatomic,weak) id <ViewController1Protocol> delegate; 

En el file VC1 .m, el método de button de rechazo debe llamar al método de delegado

 - (IBAction)buttonPressedFromVC1:(UIButton *)sender { [self.delegate dissmissAndPresentVC2] } 

Ahora, en mainVC, configúrelo como delegado de VC1 al crear VC1:

 - (IBAction)present1:(id)sender { ViewController1* vc = [[ViewController1 alloc] initWithNibName:@"ViewController1" bundle:nil]; vc.delegate = self; [self present:vc]; } 

e implementar el método delegado:

 - (void)dismissAndPresent2 { [self dismissViewControllerAnimated:NO completion:^{ [self present2:nil]; }]; } 

present2: puede ser el mismo método que su VC2Pressed: button IBAction method. Tenga en count que se llama desde el bloque de finalización para garantizar que VC2 no se presente hasta que VC1 se elimine por completo.

Ahora se está moviendo desde VC1-> VCMain-> VC2, por lo que probablemente solo desee que se active una de las transiciones.

actualizar

En sus comentarios expresa sorpresa por la complejidad requerida para lograr una cosa aparentemente simple. Te aseguro que este patrón de delegación es tan importante para gran parte de Objective-C y Cocoa, y este ejemplo trata sobre lo más simple que puedes get, que realmente deberías hacer el esfuerzo para sentirte cómodo con él.

En la Guía de progtwigción del controller View de Apple tienen esto que decir :

Descartar un controller de vista presentado

Cuando llega el momento de descartar un controller de vista presentado, el enfoque preferido es dejar que el controller de vista presentador lo rechace. En otras palabras, siempre que sea posible, el mismo controller de vista que presentó el controller de vista también debería asumir la responsabilidad de descartarlo. Aunque hay varias técnicas para notificar al controller de vista presentador que su controller de vista presentado debe ser descartado, la técnica preferida es la delegación. Para get más información, consulte "Uso de la delegación para comunicarse con otros controlleres".

Si realmente piensa en lo que quiere lograr y en cómo lo hace, se dará count de que enviar su MainViewController para hacer todo el trabajo es la única salida lógica dado que no desea usar un NavigationController. Si utiliza un NavController, en efecto está 'delegando', aunque no explícitamente, en el navController para hacer todo el trabajo. Debe haber algún object que mantenga un logging central de lo que está sucediendo con su navigation de VC, y necesita algún método de comunicación con él, haga lo que haga.

En la práctica, el consejo de Apple es un poco extremo … en casos normales, no necesita hacer un delegado y un método dedicados, puede confiar en [self presentingViewController] dismissViewControllerAnimated: es cuando, en casos como el suyo, desea que se desestime para tener otros efectos en los objects remotos que debe tener cuidado.

Aquí hay algo que podrías imaginar para funcionar sin toda la molestia de los delegates …

 - (IBAction)dismiss:(id)sender { [[self presentingViewController] dismissViewControllerAnimated:YES completion:^{ [self.presentingViewController performSelector:@selector(presentVC2:) withObject:nil]; }]; } 

Después de pedirle al controller presentador que nos descarte, tenemos un bloque de finalización que llama a un método en el ViewController que presenta para invocar VC2. No se necesita delegado. (Un gran punto de venta de bloques es que networkingucen la necesidad de delegates en estas circunstancias). Sin embargo, en este caso hay algunas cosas que se interponen en el path …

  • en VC1 no se sabe que mainVC implementa el método present2 : puede terminar con errores o fallas difíciles de depurar. Los delegates te ayudan a evitar esto.
  • una vez que se descarta VC1, en realidad no se puede ejecutar el bloque de finalización … ¿o es así? ¿Self.presentingViewController significa algo más? No sabes (yo tampoco) … con un delegado, no tienes esta incertidumbre.
  • Cuando bash ejecutar este método, simplemente se cuelga sin advertencia ni errores.

Entonces, por favor … tómese el time para aprender deleite.

actualización2

En su comentario, ha logrado hacer que funcione al usar esto en el manejador de botones de rechazo de VC2:

  [self.view.window.rootViewController dismissViewControllerAnimated:YES completion:nil]; 

Sin duda, esto es mucho más simple, pero te deja con una serie de problemas.

Acoplamiento apretado
Está uniendo cabalmente su estructura de viewController. Por ejemplo, si va a insert un nuevo viewController antes de mainVC, su comportamiento requerido se romperá (navegaría a la anterior). En VC1 también tuvo que #importar VC2. Por lo tanto, tiene muchas interdependencies, lo que rompe los objectives OOP / MVC.

Usando delegates, ni VC1 ni VC2 necesitan saber nada sobre mainVC o sus antecedentes, por lo que mantenemos todo suelto-acoplado y modular.

Memoria
VC1 no se ha ido, todavía tiene dos pointers:

  • mainVC's presentedViewController propiedad ViewController
  • La propiedad ViewController de presentingViewController de VC2

Puede probar esto iniciando session y también haciendo esto desde VC2

 [self dismissViewControllerAnimated:YES completion:nil]; 

Todavía funciona, todavía te lleva de vuelta a VC1.

Eso me parece una pérdida de memory.

La key de esto está en la advertencia que está recibiendo aquí:

 [self presentViewController:vc2 animated:YES completion:nil]; [self dismissViewControllerAnimated:YES completion:nil]; // Attempt to dismiss from view controller <VC1: 0x715e460> // while a presentation or dismiss is in progress! 

La lógica se descompone, ya que está intentando descartar el VC de presentación del cual VC2 es el VC presentado. El segundo post no se ejecuta realmente, bueno, tal vez suceden algunas cosas, pero aún le quedan dos pointers a un object que pensó que había eliminado. ( edite: he comprobado esto y no es tan malo, ambos objects desaparecen cuando regresa a mainVC )

Esa es una forma bastante larga de decir: por favor, use delegates. Si ayuda, hice otra breve descripción del patrón aquí:
¿Pasar un controller en un construtor siempre es una mala práctica?

actualización 3
Si realmente quiere evitar a los delegates, esta podría ser la mejor salida:

En VC1:

 [self presentViewController:VC2 animated:YES completion:nil]; 

Pero no descartes nada … como comprobamos, en realidad no sucede de todos modos.

En VC2:

 [self.presentingViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil]; 

Como (sabemos) no hemos descartado VC1, podemos volver a través de VC1 a MainVC. MainVC descarta VC1. Debido a que VC1 se ha ido, se presenta VC2 con él, por lo que está de vuelta en MainVC en un estado limpio.

Todavía está muy acoplado, ya que VC1 necesita saber sobre VC2, y VC2 necesita saber que se llegó a través de MainVC-> VC1, pero es lo mejor que obtendrá sin un poco de delegación explícita.

Creo que has entendido mal algunos conceptos básicos sobre los controlleres de vista modal de iOS. Cuando descarta VC1, cualquier controller de vista presentado por VC1 también se descarta. Apple pensado para que los controlleres de vista modal fluyan de manera astackda: en su caso VC2 es presentado por VC1. Está rechazando VC1 tan pronto como presenta VC2 de VC1, por lo que es un desastre total. Para lograr lo que desea, buttonPressedFromVC1 debe tener el mainCC presente VC2 inmediatamente después de que VC1 se descarte. Y creo que esto se puede lograr sin delegates. Algo a lo largo de las líneas:

 UIViewController presentingVC = [self presentingViewController]; [self dismissViewControllerAnimated:YES completion: ^{ [presentingVC presentViewController:vc2 animated:YES completion:nil]; }]; 

Tenga en count que self.presentingViewController se almacena en alguna otra variable, ya que después de que vc1 se descarte, no debe hacer ninguna reference.

Ejemplo en Swift , que muestra la explicación de la casting arriba y la documentation de Apple:

  1. Basándose en la documentation de Apple y la explicación de la casting anterior (corrigiendo algunos errores), la versión presentViewController utiliza el patrón de layout de delegado:

ViewController.swift

 import UIKit protocol ViewControllerProtocol { func dismissViewController1AndPresentViewController2() } class ViewController: UIViewController, ViewControllerProtocol { @IBAction func goToViewController1BtnPressed(sender: UIButton) { let vc1: ViewController1 = self.storyboard?.instantiateViewControllerWithIdentifier("VC1") as ViewController1 vc1.delegate = self vc1.modalTransitionStyle = UIModalTransitionStyle.FlipHorizontal self.presentViewController(vc1, animated: true, completion: nil) } func dismissViewController1AndPresentViewController2() { self.dismissViewControllerAnimated(false, completion: { () -> Void in let vc2: ViewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as ViewController2 self.presentViewController(vc2, animated: true, completion: nil) }) } } 

ViewController1.swift

 import UIKit class ViewController1: UIViewController { var delegate: protocol<ViewControllerProtocol>! @IBAction func goToViewController2(sender: UIButton) { self.delegate.dismissViewController1AndPresentViewController2() } } 

ViewController2.swift

 import UIKit class ViewController2: UIViewController { } 
  1. Basándose en la explicación de la casting anterior (corrigiendo algunos errores), la versión pushViewController utiliza el patrón de layout de delegado:

ViewController.swift

 import UIKit protocol ViewControllerProtocol { func popViewController1AndPushViewController2() } class ViewController: UIViewController, ViewControllerProtocol { @IBAction func goToViewController1BtnPressed(sender: UIButton) { let vc1: ViewController1 = self.storyboard?.instantiateViewControllerWithIdentifier("VC1") as ViewController1 vc1.delegate = self self.navigationController?.pushViewController(vc1, animated: true) } func popViewController1AndPushViewController2() { self.navigationController?.popViewControllerAnimated(false) let vc2: ViewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as ViewController2 self.navigationController?.pushViewController(vc2, animated: true) } } 

ViewController1.swift

 import UIKit class ViewController1: UIViewController { var delegate: protocol<ViewControllerProtocol>! @IBAction func goToViewController2(sender: UIButton) { self.delegate.popViewController1AndPushViewController2() } } 

ViewController2.swift

 import UIKit class ViewController2: UIViewController { } 

Radu Simionescu – ¡trabajo increíble! y debajo de Su solución para los amantes de Swift:

 @IBAction func showSecondControlerAndCloseCurrentOne(sender: UIButton) { let secondViewController = storyboard?.instantiateViewControllerWithIdentifier("ConrollerStoryboardID") as UIViewControllerClass // change it as You need it var presentingVC = self.presentingViewController self.dismissViewControllerAnimated(false, completion: { () -> Void in presentingVC!.presentViewController(secondViewController, animated: true, completion: nil) }) } 

Yo quería esto:

MapVC es un map en pantalla completa.

Cuando presiono un button, abre PopupVC (no en pantalla completa) sobre el map.

Cuando presiono un button en PopupVC, regresa a MapVC, y luego quiero ejecutar viewDidAppear.

Hice esto:

MapVC.m: en la acción del button, una segue programáticamente, y establecer delegado

 - (void) buttonMapAction{ PopupVC *popvc = [self.storyboard instantiateViewControllerWithIdentifier:@"popup"]; popvc.delegate = self; [self presentViewController:popvc animated:YES completion:nil]; } - (void)dismissAndPresentMap { [self dismissViewControllerAnimated:NO completion:^{ NSLog(@"dismissAndPresentMap"); //When returns of the other view I call viewDidAppear but you can call to other functions [self viewDidAppear:YES]; }]; } 

PopupVC.h: antes de @interface, agregue el protocolo

 @protocol PopupVCProtocol <NSObject> - (void)dismissAndPresentMap; @end 

después de @interface, una nueva propiedad

 @property (nonatomic,weak) id <PopupVCProtocol> delegate; 

PopupVC.m:

 - (void) buttonPopupAction{ //jump to dismissAndPresentMap on Map view [self.delegate dismissAndPresentMap]; }