apple - iPhone: descarta mĂșltiples ViewControllers
iphone photos (22)
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
Método universal iOS 8+ para descarte de pantalla completa sin contexto de animación incorrecto. En Objective-C y Swift
C objetivo
- (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)
Antes que nada, Oscar Peli agradece tu código.
Para iniciar su navigationController al principio, podría hacerlo un poco más dinámico de esta manera. (en caso de que no sepas la cantidad de ViewControllers en la pila)
NSArray *viewControllers = self.navigationController.viewControllers;
[self.navigationController popToViewController: [viewControllers objectAtIndex:0] animated: YES];
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];
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 dismiss(animated:completion:) .
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 pila de controladores de vista presentados, al invocar este método en un controlador de vista inferior en la pila se descarta su controlador de vista secundaria inmediato y todos los controladores de vista sobre ese niño en la pila. Cuando esto sucede, solo se descarta la vista más alta de forma animada; cualquier controlador de vista intermedio simplemente se elimina de la pila. 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 pila.
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 pila.
El problema con la mayoría de las soluciones es que cuando descarta la pila de ViewControllers presentados, el usuario verá brevemente el primer ViewController presentado en la pila 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)
}
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! :)
En Swift:
self.presentingViewController?.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
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 pila de controladores de vista modal, al invocar este método en un controlador de vista inferior en la pila se descarta su controlador de vista secundaria inmediato y todos los controladores de vista sobre ese elemento secundario en la pila. Cuando esto sucede, solo se descarta la vista más alta de forma animada; cualquier controlador de vista intermedio simplemente se elimina de la pila. 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 pila.
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
Extensión de Swift basada en las respuestas anteriores:
extension UIViewController {
func dismissUntilAnimated<T: UIViewController>(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<T: UIViewController>(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.
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()
Prueba esto..
ThirdViewController *tvc = [[ThirdViewController alloc] initWithNibName:@"ThirdViewController" bundle:nil];
[self.view addsubview:tvc];
[tvc release];
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 pila. 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 pila, puede agregar lógica basada en [vc isKindOfClass:[DesiredViewControllerClass class]]
.
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];
Si va directo al principio, puede usar el código [self.navigationController popToRootViewControllerAnimated: YES];
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 pila de navegación y decida que deberíamos volver al principio de la pila 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.
Una versión rápida con algunas adiciones basadas en this 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()
}
}
}
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 :)
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.
Swift 3 extensión basada en las respuestas anteriores.
Principio para una pila 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.
id vc = [self presentingViewController];
id lastVC = self;
while (vc != nil) {
id tmp = vc;
vc = [vc presentingViewController];
lastVC = tmp;
}
[lastVC dismissViewControllerAnimated:YES completion:^{
}];
[[self presentingViewController]presentingViewController]dismissModalViewControllerAnimated:NO];
También puede implementar un delegado en todos los controladores que desee descartar