Esta línea:
[self dismissViewControllerAnimated:YES completion:nil];
no se está enviando un mensaje a sí mismo, en realidad está enviando un mensaje a su VC presentador, pidiéndole que haga el rechazo. Cuando presenta un VC, crea una relación entre el VC que presenta y el presentado. Por lo tanto, no debe destruir el VC que se presenta mientras se está presentando (el VC presentado no puede devolver ese mensaje de descarte ...). Como realmente no lo está tomando en cuenta, está dejando la aplicación en un estado confuso. Vea mi respuesta Descartar un controlador de vista presentado
en el que recomiendo que este método esté escrito más claramente:
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
En su caso, debe asegurarse de que todo el control se realice en formato mainVC
. Debe usar un delegado para enviar el mensaje correcto a MainViewController desde ViewController1, para que mainVC pueda descartar VC1 y luego presentar VC2.
En VC2 VC1 agregue un protocolo en su archivo .h arriba de @interface:
@protocol ViewController1Protocol <NSObject>
- (void)dismissAndPresentVC2;
@end
y más abajo en el mismo archivo en la sección @interface declare una propiedad para contener el puntero delegado:
@property (nonatomic,weak) id <ViewController1Protocol> delegate;
En el archivo .m VC1, el método del botón de descarte debe llamar al método 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 el VC2Pressed:
método IBAction del botón. Tenga en cuenta que se llama desde el bloque de finalización para garantizar que VC2 no se presente hasta que VC1 se descarte por completo.
Ahora se está moviendo de VC1-> VCMain-> VC2, por lo que probablemente querrá que solo una de las transiciones esté animada.
actualizar
En sus comentarios expresa sorpresa por la complejidad necesaria para lograr algo aparentemente simple. Le aseguro que este patrón de delegación es tan fundamental para gran parte de Objective-C y Cocoa, y este ejemplo es el más simple que puede obtener, que realmente debería hacer el esfuerzo para sentirse cómodo con él.
En la Guía de programación del controlador de vista de Apple, tienen esto que decir :
Descartar un controlador de vista presentado
Cuando llega el momento de descartar un controlador de vista presentado, el enfoque preferido es dejar que el controlador de vista de presentación lo descarte. En otras palabras, siempre que sea posible, el mismo controlador de vista que presentó el controlador de vista también debe asumir la responsabilidad de descartarlo. Aunque existen varias técnicas para notificar al controlador de vista de presentación que su controlador de vista presentada debe descartarse, la técnica preferida es la delegación. Para obtener más información, consulte "Uso de la delegación para comunicarse con otros controladores".
Si realmente piensa en lo que quiere lograr y cómo lo está haciendo, se dará cuenta de que enviar mensajes a su MainViewController para que haga todo el trabajo es la única salida lógica dado que no desea utilizar un NavigationController. Si se hace uso de un NavController, en efecto, son 'delegar', aunque no de forma explícita, a la NavController que hacer todo el trabajo. Debe haber algún objeto que mantenga un seguimiento central de lo que sucede con la navegación de su VC, y necesita algún método para comunicarse con él, hagas lo que hagas.
En la práctica, el consejo de Apple es un poco extremo ... en casos normales, no es necesario crear un delegado y un método dedicados, puede confiar en [self presentingViewController] dismissViewControllerAnimated:
; es cuando, en casos como el suyo, desea que su despido tenga otros efectos en el control remoto. objetos que debes cuidar.
Aquí hay algo que podría imaginar para trabajar sin todas las molestias de los delegados ...
- (IBAction)dismiss:(id)sender {
[[self presentingViewController] dismissViewControllerAnimated:YES
completion:^{
[self.presentingViewController performSelector:@selector(presentVC2:)
withObject:nil];
}];
}
Después de pedirle al controlador de presentación que nos descarte, tenemos un bloque de finalización que llama a un método en el controlador de presentación de visualización para invocar VC2. No se necesita delegado. (Un gran punto de venta de los bloques es que reducen la necesidad de delegados en estas circunstancias). Sin embargo, en este caso hay algunas cosas que se interponen en el camino ...
- en VC1 no sabes que mainVC implementa el método
present2
; puede terminar con errores o fallas difíciles de depurar. Los delegados le ayudan a evitar esto.
- una vez que se descarta VC1, no está realmente disponible para ejecutar el bloque de finalización ... ¿o no? ¿Self.presentingViewController significa algo más? No sabes (yo tampoco) ... con un delegado, no tienes esta incertidumbre.
- Cuando intento ejecutar este método, simplemente se bloquea sin advertencias ni errores.
Así que por favor ... ¡tómate el tiempo para aprender a delegar!
actualización2
En su comentario, ha logrado que funcione usando esto en el controlador de botón de descarte 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
estrecho Está conectando la estructura de su viewController. Por ejemplo, si insertara un nuevo viewController antes de mainVC, su comportamiento requerido se interrumpiría (navegaría al anterior). En VC1 también ha tenido que #importar VC2. Por lo tanto, tiene bastantes interdependencias, lo que rompe los objetivos de OOP / MVC.
Al usar delegados, ni VC1 ni VC2 necesitan saber nada sobre mainVC o sus antecedentes, por lo que mantenemos todo acoplado libremente y modular.
La memoria
VC1 no se ha ido, todavía tiene dos indicadores:
presentedViewController
propiedad de mainVC
presentingViewController
Propiedad de VC2
Puede probar esto iniciando sesión, y también simplemente 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 memoria.
La pista de esto está en la advertencia que está recibiendo aquí:
[self presentViewController:vc2 animated:YES completion:nil];
[self dismissViewControllerAnimated:YES completion:nil];
La lógica se rompe, ya que intenta descartar el VC de presentación del cual VC2 es el VC presentado. El segundo mensaje no se ejecuta realmente; bueno, tal vez sucedan algunas cosas, pero aún te quedan dos punteros a un objeto del que pensabas que te habías deshecho. ( editar - He comprobado esto y no es tan malo, ambos objetos desaparecen cuando regresas a mainVC )
Esa es una forma bastante prolija de decir: por favor, use delegados. Si ayuda, hice otra breve descripción del patrón aquí:
¿Pasar un controlador en un constructor siempre es una mala práctica?
actualización 3
Si realmente desea evitar a los delegados, esta podría ser la mejor salida:
En VC1:
[self presentViewController:VC2
animated:YES
completion:nil];
Pero no descarte 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 que VC2 lo acompaña, por lo que está de regreso 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.
Ejemplo en Swift , que muestra la explicación de la fundición anterior y la documentación de Apple:
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 { }
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 { }
fuente
ViewController
clase de ejemplo es mainVC, ¿verdad?Creo que no entendiste algunos conceptos básicos sobre los controladores de vista modal de iOS. Cuando descarta VC1, los controladores de vista presentados por VC1 también se descartan. Apple pretendía que los controladores de vista modal fluyeran de manera apilada; en su caso, VC2 es presentado por VC1. Está descartando VC1 tan pronto como presente VC2 de VC1, por lo que es un desastre total. Para lograr lo que desea, buttonPressedFromVC1 debe tener el mainVC presente VC2 inmediatamente después de que VC1 se descarte. Y creo que esto se puede lograr sin delegados. Algo parecido a esto:
UIViewController presentingVC = [self presentingViewController]; [self dismissViewControllerAnimated:YES completion: ^{ [presentingVC presentViewController:vc2 animated:YES completion:nil]; }];
Tenga en cuenta que self.presentingViewController se almacena en alguna otra variable, porque después de que vc1 se descarta, no debe hacer ninguna referencia a él.
fuente
[self dismiss...]
sucede después de[self present...]
que finaliza? No es que esté sucediendo algo asincrónicoRadu Simionescu - ¡trabajo increíble! y a continuación 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) }) }
fuente
Yo quería esto:
MapVC es un mapa en pantalla completa.
Cuando presiono un botón, se abre PopupVC (no en pantalla completa) sobre el mapa.
Cuando presiono un botón en PopupVC, vuelve a MapVC y luego quiero ejecutar viewDidAppear.
Hice esto:
MapVC.m: en la acción del botón, un segue programáticamente y establece 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]; }
fuente
Resolví el problema usando UINavigationController al presentar. En MainVC, al presentar VC1
let vc1 = VC1() let navigationVC = UINavigationController(rootViewController: vc1) self.present(navigationVC, animated: true, completion: nil)
En VC1, cuando me gustaría mostrar VC2 y descartar VC1 al mismo tiempo (solo una animación), puedo tener una animación de empuje por
let vc2 = VC2() self.navigationController?.setViewControllers([vc2], animated: true)
Y en VC2, al cerrar el controlador de vista, como de costumbre podemos usar:
self.dismiss(animated: true, completion: nil)
fuente