webentwicklung-frage-antwort-db.com.de

iPhone - mehrere ViewControllers schließen

Ich habe eine lange View Controller-Hierarchie. 

im ersten View Controller verwende ich diesen Code: 

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

Im zweiten View Controller verwende ich diesen Code:

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

und so weiter.

Es gibt also einen Moment, in dem ich viele View-Controller habe und ich zum ersten View-Controller zurückkehren muss. Wenn ich einen Schritt zurück komme, verwende ich in jedem View-Controller diesen Code:

[self dismissModalViewControllerAnimated:YES];

Wenn ich direkt vom sechsten View Controller zum ersten zurückgehen möchte, was muss ich tun, um alle Controller gleichzeitig zu entlassen?

Vielen Dank

42
Oscar Peli

Ich habe die Lösung gefunden.

Natürlich können Sie die Lösung an der naheliegendsten Stelle finden, also lesen Sie aus der UIViewController-Referenz für die Methode dismissModalViewControllerAnimated ...

Wenn Sie mehrere modale Ansichten präsentieren Controller nacheinander und damit Erstellen Sie einen Stapel modaler Ansichten Controller, die diese Methode für eine .__ aufrufen. Ansicht des Controllers im Stack lehnt seine unmittelbare Kindansicht ab Controller und alle View-Controller über diesem Kind auf dem Stapel. Wann Dies geschieht nur in der obersten Ansicht wird auf animierte Weise abgewiesen; Alle Zwischen-View-Controller sind einfach vom Stapel genommen. Das Die oberste Ansicht wird mit .__ abgewiesen. modaler Übergangsstil, der unterscheiden sich von den Stilen, die von anderen verwendet werden Ansicht der Controller im Stapel.

es reicht also aus, dismissModalViewControllerAnimated für die Zielansicht aufzurufen. Ich habe folgenden Code verwendet:

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

zurück zu mir nach Hause gehen.

24
Oscar Peli

Ja. Es gibt bereits eine Reihe von Antworten, aber ich werde sowieso nur eine am Ende der Liste hinzufügen. Das Problem ist, dass wir einen Verweis auf den View-Controller an der Basis der Hierarchie erhalten müssen. Wie in der Antwort von @Juan Munhoes Junior können Sie die Hierarchie durchgehen, aber der Benutzer kann andere Wege wählen, daher ist dies eine ziemlich fragile Antwort. Es ist nicht schwer, diese einfache Lösung zu erweitern, wenn Sie einfach die Hierarchie durchsuchen und nach dem unteren Ende des Stapels suchen. Wenn Sie auf den untersten Punkt abweisen, erhalten Sie auch alle anderen. 

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

Dies ist einfach und flexibel: Wenn Sie im Stack nach einer bestimmten Art von View-Controller suchen möchten, können Sie eine auf [vc isKindOfClass:[DesiredViewControllerClass class]] basierende Logik hinzufügen. 

61
Suz

iOS 8+ universelle Methode für die Vollbildauflösung ohne falschen Animationskontext. In Objective-C und Swift

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

Schnell

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

Was stimmt nicht mit anderen Lösungen?

Es gibt viele Lösungen, aber keine davon zählt mit falsch abweisendem Kontext.

z.B. root A -> Presents B -> Presents C und Sie möchten das A von C verwerfen, können Sie offiziell aufrufen, indem Sie dismissViewControllerAnimated für rootViewController aufrufen. 

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

Wie auch immer Der Abruf dieser Wurzel von C führt zu rechtem Verhalten mit falschem Übergang (B zu A wäre statt C zu A gesehen worden). 


so 

Ich habe eine universelle Ablehnungsmethode erstellt. Diese Methode nimmt den aktuellen Vollbild-Schnappschuss und platziert ihn über dem präsentierten View-Controller des Empfängers und verwirft dann alles.(Beispiel: Standardmäßig aufgerufenes Abweisen von C, aber B wird wirklich als Abweisen angesehen)

15
Jakub Truhlář

Angenommen, Ihr erster View-Controller ist auch der Root-/Initial-View-Controller (derjenige, den Sie in Ihrem Storyboard als Initial-View-Controller benannt haben). Sie können es so einrichten, dass es Anforderungen abhört, um alle angezeigten Ansichtssteuerungen zu verwerfen:

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

Und in jedem anderen Ansichtscontroller auf dem Navigationsstapel, der entscheidet, dass wir zum Anfang des Navigationsstapels zurückkehren sollten:

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

Dies sollte alle modal präsentierten View-Controller mit einer Animation verwerfen und nur den Root-View-Controller belassen. Dies funktioniert auch, wenn der anfängliche View-Controller ein UINavigationController ist und der erste View-Controller als Root-View-Controller festgelegt ist.

Bonus-Tipp: Es ist wichtig, dass der Benachrichtigungsname identisch ist. Es ist wahrscheinlich eine gute Idee, diesen Benachrichtigungsnamen irgendwo in der App als Variable zu definieren, um keine Fehlkommunikation aufgrund von Tippfehlern zu erhalten.

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

Sie können auch einen Delegaten in allen Controllern implementieren, die Sie abweisen möchten

5

Wenn Sie alle Modellansicht-Controller verwenden, können Sie eine Benachrichtigung verwenden, um alle vordefinierten View-Controller zu schließen.

1.Registrieren Sie die Benachrichtigung in RootViewController wie folgt

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

2.Implementieren Sie die Funktion dismissModelViewController in rootviewController

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

3.Benachrichtigung bei jedem Schließen oder Schließen eines Schaltflächenereignisses.

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

In Swift:

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

Versuche dies..

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

Das Problem bei den meisten Lösungen ist, dass der Benutzer, wenn Sie den Stack der präsentierten viewControllers abweisen, den ersten präsentierten viewController im Stack kurz sieht, während er geschlossen wird. Die hervorragende Lösung von Jakub löst das. Hier ist eine Erweiterung, die auf seiner Antwort basiert.

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

}

Verwendung: Rufen Sie diese Erweiterungsfunktion von jedem präsentierten viewController aus auf, den Sie zum Stammverzeichnis zurückweisen möchten.

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

Eine schnelle Version mit einigen Ergänzungen basierend auf 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()
        }
    }
}

Einfache rekursive Annäherung:

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

Dadurch wird das Schließen jedes untergeordneten Controllers erzwungen und nur das Selbst animiert. Sie können nach Belieben umschalten, aber wenn Sie jeden Controller animieren, gehen Sie einen nach dem anderen und es ist langsam. 

Anruf

baseController.dismissEntireStackAndSelf()
1
Logan

Hier ist eine Lösung, die ich verwende, um alle View-Controller zu schließen und zu schließen, um zum Root-View-Controller zurückzukehren. Ich habe diese zwei Methoden in einer Kategorie von 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;
    }
}

Dann rufe ich einfach an

[UIViewController returnToRootViewController];
1
Nikolay Spassov

Schnelle Erweiterung basierend auf den obigen Antworten: 

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

Swift 3.0 Version:

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

Ich habe völlig vergessen, warum ich das gemacht habe, da es eine unglaublich dumme Logik ist, wenn man bedenkt, dass die Präsentation des View-Controllers eines modalen View-Controllers meist UITabBarController ist, was dies völlig unbrauchbar macht. Es ist viel sinnvoller, die Base View Controller-Instanz tatsächlich zu erwerben und dismiss aufzurufen.

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 Erweiterung basierend auf den obigen Antworten.

Prinzip für einen solchen Stapel: A -> B -> C -> D

  • Machen Sie einen Schnappschuss von D
  • Fügen Sie diesen Schnappschuss auf B hinzu
  • Abweisung von B ohne Animation
  • Zum Abschluss A mit Animation abmelden

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

Ein kleines Flackern am Simulator, aber nicht am Gerät.

1
squall2022

Zunächst einmal bedankt sich Oscar Peli für Ihren Code.

Um Ihren navigationController am Anfang zu starten, können Sie ihn auf diese Weise etwas dynamischer gestalten. (falls Sie die Anzahl der ViewControllers im Stack nicht kennen)

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

Für Swift 3.0+

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

Dadurch werden alle angezeigten Ansichtscontroller auf Ihrem .__ gelöscht. rootviewcontroller.

1
Bhuvan Bhatt

Verwenden Sie diese generische Lösung, um dieses Problem zu lösen:

- (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

Schließen Sie die oberste VC -Animation ab und die anderen nicht. Wenn Sie drei modale VC haben

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

BEARBEITEN: Wenn Sie dies nur mit einer Methode tun möchten, speichern Sie Ihre Hierarchie in einem Array von VC und schließen Sie das letzte animierte Objekt und die anderen Objekte nicht ab.

0
emenegro

Apple-Dokument über verwerfen (animiert: Fertigstellung :) Methode.

In Abschnitt Discussion heißt es:

any intermediate view controllers are simply removed from the stack.

Wenn Sie mehrere View-Controller nacheinander präsentieren und somit einen Stapel dargestellter View-Controller erstellen, werden beim Aufruf dieser Methode auf einem View-Controller unterhalb des Stacks der unmittelbar untergeordnete View-Controller und alle View-Controller, die über diesem Kind liegen, im Stack verworfen. In diesem Fall wird nur die oberste Ansicht auf animierte Weise verworfen. alle Zwischen-View-Controller werden einfach vom Stapel entfernt. Die oberste Ansicht wird mit ihrem modalen Übergangsstil verworfen, der sich von den Stilen unterscheiden kann, die von anderen Ansichtscontrollern verwendet werden, die sich im Stapel befinden.

Mit anderen Worten, wenn der View Controller-Stack wie folgt folgt

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

D ruft die dismiss-Methode auf, alle View-Controller verhalten sich D, zB: (E ... Z), werden aus dem Stack entfernt.

0
AechoLiu

In Swift 4 And Xcode 9 Dies wird Ihnen helfen. 

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

Genießen !!! :) 

0
Anup Gupta