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
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.
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]]
.
- (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];
}
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)
}
}
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)
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.
[[self presentingViewController]presentingViewController]dismissModalViewControllerAnimated:NO];
Vous pouvez également implémenter un délégué dans tous les contrôleurs que vous souhaitez exclure.
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];
En rapide:
self.presentingViewController?.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
Essaye ça..
ThirdViewController *tvc = [[ThirdViewController alloc] initWithNibName:@"ThirdViewController" bundle:nil];
[self.view addsubview:tvc];
[tvc release];
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)
}
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()
}
}
}
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()
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];
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.
id vc = [self presentingViewController];
id lastVC = self;
while (vc != nil) {
id tmp = vc;
vc = [vc presentingViewController];
lastVC = tmp;
}
[lastVC dismissViewControllerAnimated:YES completion:^{
}];
Swift 3 extension basée sur les réponses ci-dessus.
Principe pour une pile comme ça: A -> B -> C -> D
À 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.
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];
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.
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];
}
}
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.
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.
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 !!! :)