web-dev-qa-db-fra.com

iPhone - rejeter plusieurs ViewControllers

J'ai une longue hiérarchie de contrôleurs de vue; 

dans le premier View Controller, j'utilise ce code: 

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

Dans le deuxième contrôleur de vue, j'utilise ce code:

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

etc.

Donc, il y a un moment où j'ai plusieurs contrôleurs de vue et que je dois revenir au premier contrôleur de vue . Si je reviens une étape à la fois, j'utilise ce code dans chaque contrôleur de vue:

[self dismissModalViewControllerAnimated:YES];

Si je veux revenir directement du sixième contrôleur de vue au premier contrôleur, par exemple, que dois-je faire pour congédier tous les contrôleurs en même temps?

Merci

42
Oscar Peli

J'ai trouvé la solution.

Bien sûr, vous pouvez trouver la solution à l’endroit le plus évident. Lisez donc la référence UIViewController pour connaître la méthodegleDeModalViewControllerAnimated ...

Si vous présentez plusieurs vues modales contrôleurs successifs, et donc construire une pile de vues modales contrôleurs, appelant cette méthode sur un voir le contrôleur plus bas dans la pile rejette sa vue enfant immédiate contrôleur et tous les contrôleurs de vue au-dessus de cet enfant sur la pile. Quand cela se produit, seule la vue la plus haute est licencié de manière animée; tous les contrôleurs de vue intermédiaires sont simplement retiré de la pile. Le La vue la plus haute est rejetée à l'aide de son style de transition modale, qui peut diffèrent des styles utilisés par d’autres voir les contrôleurs plus bas dans la pile.

il suffit donc d'appeler le rejetModalViewControllerAnimated sur la vue cible . J'ai utilisé le code suivant:

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

rentrer chez moi.

24
Oscar Peli

Oui. il y a déjà beaucoup de réponses, mais je vais simplement en ajouter une à la fin de la liste. Le problème est que nous devons obtenir une référence au contrôleur de vue à la base de la hiérarchie. Comme dans la réponse de @Juan Munhoes Junior, vous pouvez vous déplacer dans la hiérarchie, mais l'utilisateur peut emprunter différentes routes, ce qui en fait une réponse assez fragile. Il n’est pas difficile d’étendre cette solution simple en marchant simplement dans la hiérarchie à la recherche du bas de la pile. Si vous appelez un licenciement en bas, tous les autres seront concernés. 

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

C’est simple et flexible: si vous souhaitez rechercher un type particulier de contrôleur de vue dans la pile, vous pouvez ajouter une logique basée sur [vc isKindOfClass:[DesiredViewControllerClass class]]

61
Suz

méthode universelle iOS 8+ pour le renvoi en plein écran sans contexte d'animation erroné. En Objective-C et Swift

Objectif c

- (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];
}

Rapide

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

Quel est le problème avec d'autres solutions?

Il existe de nombreuses solutions, mais aucune d’entre elles n’est prise en compte avec un contexte de renvoi erroné.

par exemple. racine A -> Présente B -> Présente C et que vous voulez renvoyer de A à C, vous pouvez officiellement appeler dismissViewControllerAnimated à rootViewController

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

Cependant appelez licencier sur cette racine de C conduira à un comportement correct avec une transition incorrecte (B à A aurait été vu à la place de C à A). 


alors  

J'ai créé la méthode de licenciement universel. Cette méthode prendra l’instantané plein écran actuel et le placera sur le contrôleur de vue présenté par le récepteur, puis le rejetera tout. (Exemple: Appelé par défaut, licencier de C, mais B est vraiment considéré comme un licenciement)

15
Jakub Truhlář

Supposons que votre premier contrôleur de vue est également le contrôleur de vue racine/initiale (celui que vous avez nommé dans votre Storyboard en tant que contrôleur de vue initiale). Vous pouvez le configurer pour écouter les demandes de rejeter tous ses contrôleurs de vue présentés:

dans 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:^{}];
}

Et dans tout autre contrôleur de vue situé dans la pile de navigation qui décide que nous devrions retourner au sommet de la pile de navigation:

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

Cela devrait exclure tous les contrôleurs de vue présentés de manière modale avec une animation, ne laissant que le contrôleur de vue racine. Cela fonctionne également si votre contrôleur de vue initial est un UINavigationController et que le premier contrôleur de vue est défini comme son contrôleur de vue racine.

Conseil bonus: il est important que le nom de la notification soit identique. Probablement une bonne idée de définir ce nom de notification quelque part dans l'application en tant que variable, afin d'éviter toute erreur de communication due à des erreurs de frappe.

14
Thomas Verbeek
[[self presentingViewController]presentingViewController]dismissModalViewControllerAnimated:NO];

Vous pouvez également implémenter un délégué dans tous les contrôleurs que vous souhaitez exclure.

5

Si vous utilisez tous les modèles de contrôleur de vue modèle, vous pouvez utiliser la notification pour supprimer tous les contrôleurs de vue prédéfinis.

1. Enregistrez la notification dans RootViewController comme ceci

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

2.Implémentez la fonction rejetModelViewController dans rootviewController

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

3.Notification après chaque événement de fermeture ou de fermeture.

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

En rapide:

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

Essaye ça..

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

Le problème avec la plupart des solutions est que, lorsque vous supprimez la pile de viewControllers présentés, l’utilisateur voit brièvement le premier viewController présenté dans la pile lorsqu’il est rejeté. L'excellente solution de Jakub résout ce problème. Voici une extension basée sur sa réponse.

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)
            }
        }
    }

}

Utilisation: Appelez cette fonction d'extension à partir de tout viewController présenté que vous souhaitez rejeter à la racine.

@IBAction func close() {
    dismissAll(animated: true)
}
2
Harris

Une version de Swift avec quelques ajouts basés sur this comment

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()
        }
    }
}
1

Simple récursif plus proche:

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

Cela forcera la fermeture de tous les contrôleurs enfants et n'animera que l'auto. Vous pouvez basculer pour ce que vous voulez, mais si vous animez chaque contrôleur, ils vont un par un et c'est lent. 

Appel

baseController.dismissEntireStackAndSelf()
1
Logan

Voici une solution que j'utilise pour afficher et supprimer tous les contrôleurs de vue afin de revenir au contrôleur de vue racine. J'ai ces deux méthodes dans une catégorie 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;
    }
}

Alors j'appelle

[UIViewController returnToRootViewController];
1
Nikolay Spassov

Extension rapide basée sur les réponses ci-dessus: 

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)
        })
    }
}

Version 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)
        }
    }
}

Complètement oublié la raison de ma création, car c’est une logique incroyablement stupide, étant donné que la plupart du temps, le contrôleur de présentation d’un contrôleur de vue modal est UITabBarController, ce qui le rend totalement inutile. Il est beaucoup plus judicieux d’acquérir l’instance de contrôleur de vue de base et d’appeler dismiss à ce sujet.

1
Mark Bourke
  id vc = [self presentingViewController];
  id lastVC = self;
  while (vc != nil) {
    id tmp = vc;
    vc = [vc presentingViewController];
    lastVC = tmp;
  }
  [lastVC dismissViewControllerAnimated:YES completion:^{
}];
1
Ashish Awaghad

Swift 3 extension basée sur les réponses ci-dessus.

Principe pour une pile comme ça: A -> B -> C -> D

  • Prendre un instantané de D
  • Ajouter cet instantané sur B
  • Rejeter de B sans animation
  • À la fin, quittez A avec animation

    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 petit scintillement sur le simulateur mais pas sur l'appareil.

1
squall2022

Oscar Peli, tout d’abord, merci pour votre code.

Pour démarrer votre navigationController au début, vous pouvez le rendre un peu plus dynamique de cette façon. (si vous ne connaissez pas le nombre de ViewControllers dans la pile)

NSArray *viewControllers = self.navigationController.viewControllers;
[self.navigationController popToViewController: [viewControllers objectAtIndex:0] animated: YES];
1
Ben Groot

Pour Swift 3.0+

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

Cela rejettera tous les contrôleurs de vue présentés sur votre rootviewcontroller.

1
Bhuvan Bhatt

Utilisez cette solution générique pour résoudre ce problème:

- (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];
    }
}
1
SachinVsSachin

Rejetez le haut VC animé et les autres non. Si vous avez trois VC modaux

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

EDIT: si vous voulez faire cela avec une seule méthode, enregistrez votre hiérarchie dans un tableau de VC et ignorez le dernier objet animé et les autres non.

0
emenegro

Document Apple sur licencier (animation: achèvement :) méthode.

Dans la section Discussion, il est dit:

any intermediate view controllers are simply removed from the stack.

Si vous présentez successivement plusieurs contrôleurs de vue, créant ainsi une pile de contrôleurs de vue présentés, l'appel de cette méthode sur un contrôleur de vue situé plus bas dans la pile supprime son contrôleur de vue enfant immédiat et tous les contrôleurs de vue situés au-dessus de cet enfant sur la pile. Lorsque cela se produit, seule la vue la plus haute est ignorée de manière animée. tous les contrôleurs de vue intermédiaires sont simplement retirés de la pile. La vue la plus haute est rejetée à l'aide de son style de transition modale, qui peut différer des styles utilisés par d'autres contrôleurs de vue plus bas dans la pile.

En d'autres termes, si la pile du contrôleur de vue ressemble à ce qui suit

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

D appelle la méthode dismiss, tous les contrôleurs de vue behide D, ex: (E ... Z), seront supprimés de la pile.

0
AechoLiu

Dans Swift 4 Et Xcode 9 Cela vous aidera. 

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

Prendre plaisir !!! :) 

0
Anup Gupta