iPhone: descarta múltiples ViewControllers

Tengo una jerarquía de Controladores de Vista larga;

en el primer Controlador de Vista utilizo este código:

SecondViewController *svc = [[SecondViewController alloc] initWithNibName:@"SecondViewController" bundle:nil]; [self presentModalViewController:svc animated:YES]; [svc release]; 

En el segundo controlador de vista, uso este código:

 ThirdViewController *tvc = [[ThirdViewController alloc] initWithNibName:@"ThirdViewController" bundle:nil]; [self presentModalViewController:tvc animated:YES]; [tvc release]; 

y así.

Entonces, hay un momento en que tengo muchos Controladores de Vista y necesito regresar al primer Controlador de Vista. Si regreso un paso a la vez, uso en cada controlador de vista este código:

 [self dismissModalViewControllerAnimated:YES]; 

Si quiero volver directamente desde, digamos, sexto Controlador de Vista al primero, ¿qué debo hacer para descartar todos los Controladores a la vez?

Gracias

Sí. ya hay un montón de respuestas, pero de todos modos voy a agregar una al final de la lista. El problema es que necesitamos obtener una referencia al controlador de vista en la base de la jerarquía. Al igual que en la respuesta de @Juan Munhoes Junior, puede recorrer la jerarquía, pero puede haber diferentes rutas que el usuario podría tomar, por lo que esa es una respuesta bastante frágil. No es difícil extender esta simple solución, aunque simplemente caminar por la jerarquía buscando el final de la stack. Ignorar llamadas en la parte inferior también obtendrá todas las demás.

 -(void)dismissModalStack { UIViewController *vc = self.presentingViewController; while (vc.presentingViewController) { vc = vc.presentingViewController; } [vc dismissViewControllerAnimated:YES completion:NULL]; } 

Esto es simple y flexible: si desea buscar un tipo particular de controlador de vista en la stack, puede agregar lógica basada en [vc isKindOfClass:[DesiredViewControllerClass class]] .

Encontré la solución.

Por supuesto, puede encontrar la solución en el lugar más obvio para leer desde la referencia de UIViewController para el método dismissModalViewControllerAnimated …

Si presenta varios controladores de vista modal sucesivamente y crea una stack de controladores de vista modal, al invocar este método en un controlador de vista inferior en la stack se descarta su controlador de vista secundaria inmediato y todos los controladores de vista sobre ese elemento secundario en la stack. Cuando esto sucede, solo se descarta la vista más alta de forma animada; cualquier controlador de vista intermedio simplemente se elimina de la stack. La vista superior se descarta utilizando su estilo de transición modal, que puede diferir de los estilos utilizados por otros controladores de vista inferiores en la stack.

por lo tanto, es suficiente llamar a dismissModalViewControllerAnimated en la vista de destino. Use el siguiente código:

 [[[[[self parentViewController] parentViewController] parentViewController] parentViewController] dismissModalViewControllerAnimated:YES]; 

para volver a mi casa

Supongamos que su primer controlador de visualización es también el Controlador de vista raíz / inicial (el que nominó en su Guión gráfico como el Controlador de vista inicial). Puede configurarlo para escuchar las solicitudes para descartar todos sus controladores de vista presentados:

en FirstViewController:

 - (void)viewDidLoad { [super viewDidLoad]; // listen to any requests to dismiss all stacked view controllers [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dismissAllViewControllers:) name:@"YourDismissAllViewControllersIdentifier" object:nil]; // the remainder of viewDidLoad ... } // this method gets called whenever a notification is posted to dismiss all view controllers - (void)dismissAllViewControllers:(NSNotification *)notification { // dismiss all view controllers in the navigation stack [self dismissViewControllerAnimated:YES completion:^{}]; } 

Y en cualquier otro controlador de vista, baje la stack de navegación y decida que deberíamos volver al principio de la stack de navegación:

 [[NSNotificationCenter defaultCenter] postNotificationName:@"YourDismissAllViewControllersIdentifier" object:self]; 

Esto debería descartar todos los controladores de vista presentados de forma modal con una animación, dejando solo el controlador de vista raíz. Esto también funciona si su controlador de vista inicial es un UINavigationController y el primer controlador de vista está configurado como su controlador de vista raíz.

Consejo de bonificación: es importante que el nombre de la notificación sea idéntico. Probablemente sea una buena idea definir este nombre de notificación en alguna parte de la aplicación como una variable, para no generar una mala comunicación debido a errores de tipeo.

Método universal iOS 8+ para descarte de pantalla completa sin contexto de animación incorrecto. En Objective-C y Swift

C objective

 - (void)dismissModalStackAnimated:(bool)animated completion:(void (^)(void))completion { UIView *fullscreenSnapshot = [[UIApplication sharedApplication].delegate.window snapshotViewAfterScreenUpdates:false]; [self.presentedViewController.view insertSubview:fullscreenSnapshot atIndex:NSIntegerMax]; [self dismissViewControllerAnimated:animated completion:completion]; } 

Rápido

 func dismissModalStack(animated: Bool, completion: (() -> Void)?) { if let fullscreenSnapshot = UIApplication.shared.delegate?.window??.snapshotView(afterScreenUpdates: false) { presentedViewController?.view.addSubview(fullscreenSnapshot) } if !isBeingDismissed { dismiss(animated: animated, completion: completion) } } 

tl; dr

¿Qué pasa con otras soluciones?

Hay muchas soluciones, pero ninguna de ellas cuenta con un contexto de rechazo incorrecto así que:

por ejemplo, raíz A -> Presenta B -> Presenta C y desea desestimar a A desde C, puede invocar dismissViewControllerAnimated rootViewController dismissViewControllerAnimated en rootViewController .

  [[UIApplication sharedApplication].delegate.window.rootViewController dismissModalStackAnimated:true completion:nil]; 

Sin embargo, la exclusión de llamada en esta raíz de C conducirá a un comportamiento correcto con una transición incorrecta (se habría visto B a A en lugar de C a A).


asi que

Creé el método de descarte universal. Este método tomará una instantánea de pantalla completa actual y la colocará sobre el controlador de vista presentado por el receptor y luego lo descartará. (Ejemplo: el rechazo predeterminado llamado de C, pero B realmente se ve como descartar)

 [[self presentingViewController]presentingViewController]dismissModalViewControllerAnimated:NO]; 

También puede implementar un delegado en todos los controladores que desee descartar

Si está utilizando todos son controlador de vista de modelo, puede usar la notificación para descartar todo el controlador de vista preseleccionado.

1.Registrar notificación en RootViewController como este

 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dismissModelViewController) name:dismissModelViewController object:nil]; 

2. Implementar la función dismissModelViewController en rootviewController

 - (void)dismissModelViewController { While (![self.navigationController.visibleViewController isMemberOfClass:[RootviewController class]]) { [self.navigationController.visibleViewController dismissViewControllerAnimated:NO completion:nil]; } } 

3. Notificación después de cada evento de cierre o cierre del botón.

  [[NSNotificationCenter defaultCenter] postNotificationName:dismissModelViewController object:self]; 

En Swift:

 self.presentingViewController?.presentingViewController?.dismissViewControllerAnimated(true, completion: nil) 

Prueba esto..

 ThirdViewController *tvc = [[ThirdViewController alloc] initWithNibName:@"ThirdViewController" bundle:nil]; [self.view addsubview:tvc]; [tvc release]; 

Antes que nada, Oscar Peli agradece tu código.

Para iniciar su navigationController al principio, podría hacerlo un poco más dynamic de esta manera. (en caso de que no sepas la cantidad de ViewControllers en la stack)

 NSArray *viewControllers = self.navigationController.viewControllers; [self.navigationController popToViewController: [viewControllers objectAtIndex:0] animated: YES]; 
  id vc = [self presentingViewController]; id lastVC = self; while (vc != nil) { id tmp = vc; vc = [vc presentingViewController]; lastVC = tmp; } [lastVC dismissViewControllerAnimated:YES completion:^{ }]; 

Use esta solución genérica … para resolver este problema.

 - (UIViewController*)topViewController { UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController; while (topController.presentedViewController) { topController = topController.presentedViewController; } return topController; } - (void)dismissAllModalController{ __block UIViewController *topController = [self topViewController]; while (topController.presentingViewController) { [topController dismissViewControllerAnimated:NO completion:^{ }]; topController = [self topViewController]; } } 

Por favor usa esto … Feliz Codificación 🙂

Aquí hay una solución que utilizo para abrir y cerrar todos los controladores de vista para volver al controlador de vista raíz. Tengo esos dos métodos en una categoría de UIViewController:

 + (UIViewController*)topmostViewController { UIViewController* vc = [[[UIApplication sharedApplication] keyWindow] rootViewController]; while(vc.presentedViewController) { vc = vc.presentedViewController; } return vc; } + (void)returnToRootViewController { UIViewController* vc = [UIViewController topmostViewController]; while (vc) { if([vc isKindOfClass:[UINavigationController class]]) { [(UINavigationController*)vc popToRootViewControllerAnimated:NO]; } if(vc.presentingViewController) { [vc dismissViewControllerAnimated:NO completion:^{}]; } vc = vc.presentingViewController; } } 

Entonces solo llamo

 [UIViewController returnToRootViewController]; 

Una versión rápida con algunas adiciones basadas en este comentario

 func dismissModalStack(viewController: UIViewController, animated: Bool, completionBlock: BasicBlock?) { if viewController.presentingViewController != nil { var vc = viewController.presentingViewController! while (vc.presentingViewController != nil) { vc = vc.presentingViewController!; } vc.dismissViewControllerAnimated(animated, completion: nil) if let c = completionBlock { c() } } } 

Más cercano recursivo simple:

 extension UIViewController { final public func dismissEntireStackAndSelf(animate: Bool = true) { // Always false on non-calling controller presentedViewController?.ip_dismissEntireStackAndSelf(false) self.dismissViewControllerAnimated(animate, completion: nil) } } 

Esto obligará a cerrar cada controlador de niño y luego solo se animará a sí mismo. Puede alternar para lo que quiera, pero si anima cada controlador van uno por uno y es lento.

Llamada

 baseController.dismissEntireStackAndSelf() 

Swift 3 extensión basada en las respuestas anteriores.

Principio para una stack como esa: A -> B -> C -> D

  • Tome una instantánea de D
  • Agregue esta instantánea en B
  • Descartar de B sin animación
  • Al finalizar, descarte de A con animación

     extension UIViewController { func dismissModalStack(animated: Bool, completion: (() -> Void)?) { let fullscreenSnapshot = UIApplication.shared.delegate?.window??.snapshotView(afterScreenUpdates: false) if !isBeingDismissed { var rootVc = presentingViewController while rootVc?.presentingViewController != nil { rootVc = rootVc?.presentingViewController } let secondToLastVc = rootVc?.presentedViewController if fullscreenSnapshot != nil { secondToLastVc?.view.addSubview(fullscreenSnapshot!) } secondToLastVc?.dismiss(animated: false, completion: { rootVc?.dismiss(animated: true, completion: completion) }) } } } 

Un pequeño parpadeo en el simulador pero no en el dispositivo.

Extensión de Swift basada en las respuestas anteriores:

 extension UIViewController { func dismissUntilAnimated(animated: Bool, viewController: T.Type, completion: ((viewController: T) -> Void)?) { var vc = presentingViewController! while let new = vc.presentingViewController where !(new is T) { vc = new } vc.dismissViewControllerAnimated(animated, completion: { completion?(viewController: vc as! T) }) } } 

Versión de Swift 3.0:

 extension UIViewController { /// Dismiss all modally presented view controllers until a specified view controller is reached. If no view controller is found, this function will do nothing. /// - Parameter reached: The type of the view controller to dismiss until. /// - Parameter flag: Pass `true` to animate the transition. /// - Parameter completion: The block to execute after the view controller is dismissed. This block contains the instance of the `presentingViewController`. You may specify `nil` for this parameter. func dismiss(until reached: T.Type, animated flag: Bool, completion: ((T) -> Void)? = nil) { guard let presenting = presentingViewController as? T else { return presentingViewController?.dismiss(until: reached, animated: flag, completion: completion) ?? () } presenting.dismiss(animated: flag) { completion?(presenting) } } } 

Olvidé por completo por qué hice esto, ya que es una lógica increíblemente estúpida ya que la mayoría de las veces el controlador de visualización de vista modal del controlador es UITabBarController haciendo que esto sea completamente inútil. Tiene mucho más sentido adquirir realmente la instancia de controlador de vista base y dismiss llamada al respecto.

Para Swift 3.0+

 self.view.window!.rootViewController?.dismissViewControllerAnimated(false, completion: nil) 

Esto eliminará todos los controladores de vista presentados en su controlador de vistas de root.

El problema con la mayoría de las soluciones es que cuando descarta la stack de ViewControllers presentados, el usuario verá brevemente el primer ViewController presentado en la stack cuando se descarta. La excelente solución de Jakub lo resuelve. Aquí hay una extensión basada en su respuesta.

 extension UIViewController { func dismissAll(animated: Bool, completion: (() -> Void)? = nil) { if let optionalWindow = UIApplication.shared.delegate?.window, let window = optionalWindow, let rootViewController = window.rootViewController, let presentedViewController = rootViewController.presentedViewController { if let snapshotView = window.snapshotView(afterScreenUpdates: false) { presentedViewController.view.addSubview(snapshotView) presentedViewController.modalTransitionStyle = .coverVertical } if !isBeingDismissed { rootViewController.dismiss(animated: animated, completion: completion) } } } } 

Uso: llame a esta función de extensión desde cualquier viewController presentado que desee descartar nuevamente en la raíz.

 @IBAction func didTouchDoneButton() { dismissAll(animated: true) } 

Descarta el VC superior animado y los demás no. Si tiene tres VC modales

 [self dismissModalViewControllerAnimated:NO]; // First [self dismissModalViewControllerAnimated:NO]; // Second [self dismissModalViewControllerAnimated:YES]; // Third 

EDITAR: si desea hacer esto solo con un método, guarde su jerarquía en una matriz de VC y descarte el último objeto animado y los demás no.

Documento de Apple sobre el método de descarte (animación: finalización 🙂 .

En la sección Discussion , dijo:

 any intermediate view controllers are simply removed from the stack. 

Si presenta varios controladores de vista sucesivamente, construyendo una stack de controladores de vista presentados, al invocar este método en un controlador de vista inferior en la stack se descarta su controlador de vista secundaria inmediato y todos los controladores de vista sobre ese niño en la stack. Cuando esto sucede, solo se descarta la vista más alta de forma animada; cualquier controlador de vista intermedio simplemente se elimina de la stack. La vista superior se descarta utilizando su estilo de transición modal, que puede diferir de los estilos utilizados por otros controladores de vista inferiores en la stack.

En otras palabras, si el controlador de vista se acumula como sigue

 Root -> A -> B -> C -> D ... -> Z 

D llamadas método de dismiss , todos los controladores de vista antes de D , ex: (E ... Z) , serán eliminados de la stack.

En veloz 4 Y Xcode 9 Esto te ayudará.

 var vc : UIViewController = self.presentingViewController! while ((vc.presentingViewController) != nil) { vc = vc.presentingViewController! } vc.dismiss(animated: true, completion: nil) 

¡Disfruta! 🙂

Si va directo al principio, puede usar el código [self.navigationController popToRootViewControllerAnimated: YES];