Erkennen, wann die Zurück-Taste auf einer Navigationsleiste gedrückt wird

134

Ich muss einige Aktionen ausführen, wenn die Schaltfläche Zurück (Zurück zum vorherigen Bildschirm, Zurück zur übergeordneten Ansicht) auf einer Navigationsleiste gedrückt wird.

Gibt es eine Methode, die ich implementieren kann, um das Ereignis abzufangen und einige Aktionen auszulösen, um Daten anzuhalten und zu speichern, bevor der Bildschirm verschwindet?

ewok
quelle
1
Schauen Sie sich die Lösung in diesem Thread an
Jiri Volejnik
Ich habe es so gemacht, um die Entscheidung hier zu zeigen
Taras

Antworten:

316

UPDATE: Laut einigen Kommentaren scheint die Lösung in der ursprünglichen Antwort unter bestimmten Szenarien in iOS 8+ nicht zu funktionieren. Ich kann ohne weitere Details nicht überprüfen, ob dies tatsächlich der Fall ist.

Für diejenigen von Ihnen gibt es jedoch in dieser Situation eine Alternative. Durch Überschreiben kann festgestellt werden, wann ein Ansichts-Controller geöffnet wird willMove(toParentViewController:). Die Grundidee ist, dass ein Ansichts-Controller angezeigt wird, wenn er angezeigt parentwird nil.

Weitere Informationen finden Sie unter "Implementieren eines Container View Controllers" .


Seit iOS 5 habe ich festgestellt, dass der einfachste Weg, mit dieser Situation umzugehen, die neue Methode ist - (BOOL)isMovingFromParentViewController:

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];

  if (self.isMovingFromParentViewController) {
    // Do your stuff here
  }
}

- (BOOL)isMovingFromParentViewController Dies ist sinnvoll, wenn Sie Controller in einem Navigationsstapel verschieben und platzieren.

Wenn Sie jedoch modale Ansichtssteuerungen präsentieren, sollten Sie - (BOOL)isBeingDismissedstattdessen Folgendes verwenden :

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];

  if (self.isBeingDismissed) {
    // Do your stuff here
  }
}

Wie in dieser Frage erwähnt , können Sie beide Eigenschaften kombinieren:

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];

  if (self.isMovingFromParentViewController || self.isBeingDismissed) {
    // Do your stuff here
  }
}

Andere Lösungen beruhen auf der Existenz von a UINavigationBar. Stattdessen mag ich meinen Ansatz eher, weil er die erforderlichen Aufgaben von der Aktion, die das Ereignis ausgelöst hat, entkoppelt, dh eine Zurück-Taste drückt.

Elitalon
quelle
Ich mag es, wenn du antwortest. Aber warum haben Sie 'self.isBeingDismissed' verwendet? In meinem Fall werden die Anweisungen in 'self.isBeingDismissed' nicht implementiert.
Rutvij Kotecha
3
self.isMovingFromParentViewControllerhat den Wert TRUE, wenn ich den Navigationsstapel programmgesteuert mit öffne popToRootViewControllerAnimated- ohne die Zurück-Taste zu berühren. Sollte ich Ihre Antwort ablehnen? (Das Thema sagt "'Zurück' Taste ist auf einer Navigationsleiste gedrückt")
Kas-Kad
2
Tolle Antwort, vielen Dank. In Swift habe ich verwendet:override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) if isMovingFromParentViewController(){ println("back button pressed") } }
Camillo Visini
1
Sie sollten dies nur innerhalb tun, -viewDidDisappear:da es möglich ist, dass Sie ein -viewWillDisappear:ohne ein erhalten -viewDidDisappear:(wie wenn Sie mit dem Wischen beginnen, um ein Navigationscontroller-Element zu schließen und dieses Wischen dann abzubrechen.
Heath Borders
3
Sieht aus wie keine zuverlässige Lösung mehr. Arbeitete zu der Zeit, als ich dies zum ersten Mal benutzte (es war iOS 10). Aber jetzt habe ich versehentlich festgestellt, dass es ruhig nicht mehr funktioniert (iOS 11). Musste zur Lösung "willMove (toParentViewController)" wechseln.
Vitalii
100

Während viewWillAppear()und viewDidDisappear() werden aufgerufen , wenn die Zurück - Taste abgegriffen wird, werden sie auch zu anderen Zeiten genannt. Weitere Informationen hierzu finden Sie am Ende der Antwort.

Verwenden von UIViewController.parent

Das Erkennen der Zurück-Schaltfläche ist besser, wenn der VC mithilfe von willMoveToParentViewController(_:)OR von seinem übergeordneten Element (dem NavigationController) entfernt wirddidMoveToParentViewController()

Wenn übergeordnetes Element Null ist, wird der Ansichts-Controller vom Navigationsstapel entfernt und geschlossen. Wenn übergeordnetes Element nicht Null ist, wird es dem Stapel hinzugefügt und angezeigt.

// Objective-C
-(void)willMoveToParentViewController:(UIViewController *)parent {
     [super willMoveToParentViewController:parent];
    if (!parent){
       // The back button was pressed or interactive gesture used
    }
}


// Swift
override func willMove(toParent parent: UIViewController?) {
    super.willMove(toParent: parent)
    if parent == nil {
        // The back button was pressed or interactive gesture used
    }
}

Swap - out willMovefür didMoveund Check self.parent zu tun Arbeit nach der View - Controller des Feldes verwiesen.

Die Entlassung stoppen

Beachten Sie, dass Sie beim Überprüfen des übergeordneten Elements den Übergang nicht "anhalten" können, wenn Sie eine Art asynchrones Speichern durchführen müssen. Dazu können Sie Folgendes implementieren. Der einzige Nachteil hierbei ist, dass Sie den ausgefallenen iOS-gestalteten / animierten Zurück-Button verlieren. Seien Sie auch hier vorsichtig mit der interaktiven Wischgeste. Verwenden Sie Folgendes, um diesen Fall zu behandeln.

var backButton : UIBarButtonItem!

override func viewDidLoad() {
    super.viewDidLoad()

     // Disable the swipe to make sure you get your chance to save
     self.navigationController?.interactivePopGestureRecognizer.enabled = false

     // Replace the default back button
    self.navigationItem.setHidesBackButton(true, animated: false)
    self.backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Plain, target: self, action: "goBack")
    self.navigationItem.leftBarButtonItem = backButton
}

// Then handle the button selection
func goBack() {
    // Here we just remove the back button, you could also disabled it or better yet show an activityIndicator
    self.navigationItem.leftBarButtonItem = nil
    someData.saveInBackground { (success, error) -> Void in
        if success {
            self.navigationController?.popViewControllerAnimated(true)
            // Don't forget to re-enable the interactive gesture
            self.navigationController?.interactivePopGestureRecognizer.enabled = true
        }
        else {
            self.navigationItem.leftBarButtonItem = self.backButton
            // Handle the error
        }
    }
}


Weitere Informationen werden / wurden angezeigt

Wenn Sie das viewWillAppear viewDidDisappearProblem nicht erhalten haben , lassen Sie uns ein Beispiel durchgehen. Angenommen, Sie haben drei Ansichts-Controller:

  1. ListVC: Eine Tabellenansicht der Dinge
  2. DetailVC: Details zu einer Sache
  3. SettingsVC: Einige Optionen für eine Sache

Lets folgen auf die die Anrufe , detailVCwie Sie von unterwegs listVCauf settingsVCund zurück zulistVC

Liste> Detail (Push DetailVC) Detail.viewDidAppear<-
Detail anzeigen > Einstellungen (Push SettingsVC) Detail.viewDidDisappear<- verschwinden

Und wenn wir zurückgehen ...
Einstellungen> Detail (Pop-EinstellungenVC) Detail.viewDidAppear<-
Detail anzeigen > Liste (Pop-DetailVC) Detail.viewDidDisappear<- verschwinden

Beachten Sie, dass dies viewDidDisappearmehrmals aufgerufen wird, nicht nur beim Zurückgehen, sondern auch beim Vorwärtsgehen. Für einen schnellen Vorgang, der möglicherweise erwünscht ist, für einen komplexeren Vorgang wie das Speichern eines Netzwerkaufrufs jedoch möglicherweise nicht.

WCByrne
quelle
Nur eine Anmerkung, der Benutzer didMoveToParantViewController:muss arbeiten, wenn die Ansicht nicht mehr sichtbar ist. Hilfreich für iOS7 mit dem interaktivenGesutre
WCByrne
didMoveToParentViewController * Es gibt einen Tippfehler
thewormsterror
Vergessen Sie nicht, [super willMoveToParentViewController: parent] aufzurufen!
ScottyB
2
Der übergeordnete Parameter ist null, wenn Sie zum übergeordneten Ansichtscontroller wechseln, und nicht null, wenn die Ansicht angezeigt wird, in der diese Methode angezeigt wird. Sie können diese Tatsache verwenden, um eine Aktion nur auszuführen, wenn die Zurück-Taste gedrückt wird, und nicht, wenn Sie zur Ansicht gelangen. Das war schließlich die ursprüngliche Frage. :)
Mike
1
Dies wird auch bei programmgesteuerter Verwendung aufgerufen _ = self.navigationController?.popViewController(animated: true), sodass es nicht nur beim Drücken der Zurück-Taste aufgerufen wird. Ich suche einen Anruf, der nur funktioniert , wenn Zurück gedrückt wird.
Ethan Allen
16

Erste Methode

- (void)didMoveToParentViewController:(UIViewController *)parent
{
    if (![parent isEqual:self.parentViewController]) {
         NSLog(@"Back pressed");
    }
}

Zweite Methode

-(void) viewWillDisappear:(BOOL)animated {
    if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound) {
       // back button was pressed.  We know this is true because self is no longer
       // in the navigation stack.  
    }
    [super viewWillDisappear:animated];
}
Zar E Ahmer
quelle
1
Die zweite Methode war die einzige, die für mich funktioniert hat. Die erste Methode wurde auch aufgerufen, um meine Ansicht zu präsentieren, was für meinen Anwendungsfall nicht akzeptabel war.
Markshilling
10

Diejenigen, die behaupten, dass dies nicht funktioniert, irren sich:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if self.isMovingFromParent {
        print("we are being popped")
    }
}

Das funktioniert gut. Was verursacht also den weit verbreiteten Mythos, dass dies nicht der Fall ist?

Das Problem scheint auf eine fehlerhafte Implementierung einer anderen Methode zurückzuführen zu sein, nämlich auf die Implementierung eines willMove(toParent:)vergessenen Aufrufs super.

Wenn Sie willMove(toParent:)ohne Aufruf implementieren super, self.isMovingFromParentwird dies sein falseund die Verwendung von viewWillDisappearscheint fehlzuschlagen. Es ist nicht gescheitert; du hast es kaputt gemacht.

HINWEIS: Das eigentliche Problem besteht normalerweise darin, dass der zweite Ansichts-Controller erkennt, dass der erste Ansichts-Controller geöffnet wurde. Bitte beachten Sie auch die allgemeinere Diskussion hier: Unified UIViewController "wurde zur vordersten" Erkennung?

BEARBEITEN Ein Kommentar schlägt vor, dass dies viewDidDisappeareher sein sollte als viewWillDisappear.

matt
quelle
Dieser Code wird ausgeführt, wenn die Zurück-Taste gedrückt wird, wird jedoch auch ausgeführt, wenn der VC programmgesteuert geöffnet wird.
Biomiker
@biomiker Sicher, aber das würde auch für die anderen Ansätze gelten. Knallen ist Knallen. Die Frage ist, wie man einen Pop erkennt, wenn man nicht programmgesteuert popt. Wenn Sie programmgesteuert popen, wissen Sie bereits, dass Sie poppen, sodass nichts zu erkennen ist.
Matt
Ja, dies gilt für einige der anderen Ansätze, und viele davon haben ähnliche Kommentare. Ich habe nur klargestellt, da dies eine kürzliche Antwort mit einer bestimmten Gegenargumentation war und ich meine Hoffnungen geweckt hatte, als ich sie las. Für die Aufzeichnung ist die Frage jedoch, wie ein Drücken der Zurück-Taste erkannt wird. Es ist ein vernünftiges Argument zu sagen, dass Code, der auch in Situationen ausgeführt wird, in denen die Zurück-Taste nicht gedrückt wird, ohne anzugeben, ob die Zurück-Taste gedrückt wurde oder nicht, die eigentliche Frage nicht vollständig löst, selbst wenn die Frage möglicherweise mehr hätte sein können explizit in diesem Punkt.
Biomiker
1
Leider kehrt dies truefür die interaktive Wisch-Pop-Geste - vom linken Rand des Ansichts-Controllers - zurück, selbst wenn der Wisch sie nicht vollständig platzierte. Anstatt es willDisappeareinzuchecken, didDisappearfunktioniert es also.
Badhanganesh
1
@badhanganesh Danke, bearbeitete Antwort, um diese Informationen aufzunehmen.
Matt
9

Ich habe zwei Tage lang mit diesem Problem gespielt (oder gekämpft). IMO ist der beste Ansatz, einfach eine Erweiterungsklasse und ein Protokoll wie folgt zu erstellen:

@protocol UINavigationControllerBackButtonDelegate <NSObject>
/**
 * Indicates that the back button was pressed.
 * If this message is implemented the pop logic must be manually handled.
 */
- (void)backButtonPressed;
@end

@interface UINavigationController(BackButtonHandler)
@end

@implementation UINavigationController(BackButtonHandler)
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
    UIViewController *topViewController = self.topViewController;
    BOOL wasBackButtonClicked = topViewController.navigationItem == item;
    SEL backButtonPressedSel = @selector(backButtonPressed);
    if (wasBackButtonClicked && [topViewController respondsToSelector:backButtonPressedSel]) {
        [topViewController performSelector:backButtonPressedSel];
        return NO;
    }
    else {
        [self popViewControllerAnimated:YES];
        return YES;
    }
}
@end

Dies funktioniert, da jedes Mal, wenn ein View Controller geöffnet UINavigationControllerwird, ein Anruf navigationBar:shouldPopItem:eingeht. Dort erkennen wir, ob der Rücken gedrückt wurde oder nicht (jede andere Taste). Das einzige, was Sie tun müssen, ist das Protokoll in der Ansichtssteuerung zu implementieren, in der die Zurück-Taste gedrückt wird.

Denken Sie daran, den View Controller manuell einzublenden backButtonPressedSel, wenn alles in Ordnung ist.

Wenn Sie bereits Unterklassen erstellt UINavigationViewControllerund implementiert navigationBar:shouldPopItem:haben, machen Sie sich keine Sorgen, dies wird es nicht stören.

Möglicherweise möchten Sie auch die Rückengeste deaktivieren.

if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
    self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
7ynk3r
quelle
1
Diese Antwort war für mich fast vollständig, außer dass ich feststellte, dass 2 Viewcontroller oft geknallt wurden. Wenn Sie YES zurückgeben, ruft die aufrufende Methode pop auf. Wenn Sie also auch pop aufrufen, werden 2 Viewcontroller angezeigt. Sehen Sie diese Antwort auf einer anderen Frage für mehr Details (eine sehr gute Antwort, die mehr Upvotes verdient): stackoverflow.com/a/26084150/978083
Jason Ridge
Guter Punkt, meine Beschreibung war über diese Tatsache nicht klar. Das "Denken Sie daran, den Ansichts-Controller manuell zu öffnen, wenn alles in Ordnung ist" gilt nur für den Fall, dass "NEIN" zurückgegeben wird, andernfalls ist der Fluss der normale Pop.
7ynk3r
1
Für den Zweig "else" ist es besser, die Super-Implementierung aufzurufen, wenn Sie nicht selbst mit Pop umgehen möchten, und es zurückgeben zu lassen, was es für richtig hält, was meistens JA ist, aber es kümmert sich dann auch um Pop selbst und animiert Chevron richtig .
Ben Sinclair
9

Dies funktioniert für mich in iOS 9.3.x mit Swift:

override func didMoveToParentViewController(parent: UIViewController?) {
    super.didMoveToParentViewController(parent)

    if parent == self.navigationController?.parentViewController {
        print("Back tapped")
    }
}

Im Gegensatz zu anderen Lösungen hier scheint dies nicht unerwartet auszulösen.

Chris Villa
quelle
Es ist besser, stattdessen willMove zu verwenden
Eugene Gordin
4

Für die Aufzeichnung denke ich, dass dies mehr von dem ist, wonach er gesucht hat…

    UIBarButtonItem *l_backButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRewind target:self action:@selector(backToRootView:)];

    self.navigationItem.leftBarButtonItem = l_backButton;


    - (void) backToRootView:(id)sender {

        // Perform some custom code

        [self.navigationController popToRootViewControllerAnimated:YES];
    }
Paul Brady
quelle
1
Danke Paul, diese Lösung ist ganz einfach. Leider ist das Symbol anders. Dies ist das Symbol "Zurückspulen", nicht das Symbol "Zurück". Vielleicht gibt es eine Möglichkeit, das Zurück-Symbol zu verwenden ...
Ferran Maylinch
2

Wie purrrminatorgesagt, die Antwort von elitalonist nicht ganz richtig, da your stuffsie auch ausgeführt werden würde, wenn die Steuerung programmgesteuert geöffnet wird.

Die Lösung, die ich bisher gefunden habe, ist nicht sehr schön, aber sie funktioniert für mich. Neben dem elitalonGesagten überprüfe ich auch, ob ich programmgesteuert auftauche oder nicht:

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];

  if ((self.isMovingFromParentViewController || self.isBeingDismissed)
      && !self.isPoppingProgrammatically) {
    // Do your stuff here
  }
}

Sie müssen diese Eigenschaft zu Ihrem Controller hinzufügen und auf YES setzen, bevor Sie programmgesteuert einspringen:

self.isPoppingProgrammatically = YES;
[self.navigationController popViewControllerAnimated:YES];

Danke für Ihre Hilfe!

Ferran Maylinch
quelle
2

Am besten verwenden Sie die UINavigationController-Delegatmethoden

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated

Auf diese Weise können Sie erkennen, welcher Controller den UINavigationController anzeigt.

if ([viewController isKindOfClass:[HomeController class]]) {
    NSLog(@"Show home controller");
}
Harald
quelle
Dies sollte als die richtige Antwort markiert werden! Vielleicht möchten Sie auch noch eine Zeile hinzufügen, um die Leute daran zu erinnern -> self.navigationController.delegate = self;
Mike Critchley
2

Ich habe dieses Problem gelöst, indem ich der Navigationsleiste auf der linken Seite ein UIControl hinzugefügt habe.

UIControl *leftBarItemControl = [[UIControl alloc] initWithFrame:CGRectMake(0, 0, 90, 44)];
[leftBarItemControl addTarget:self action:@selector(onLeftItemClick:) forControlEvents:UIControlEventTouchUpInside];
self.leftItemControl = leftBarItemControl;
[self.navigationController.navigationBar addSubview:leftBarItemControl];
[self.navigationController.navigationBar bringSubviewToFront:leftBarItemControl];

Und Sie müssen daran denken, es zu entfernen, wenn die Ansicht verschwindet:

- (void) viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    if (self.leftItemControl) {
        [self.leftItemControl removeFromSuperview];
    }    
}

Das ist alles!

Eric
quelle
2

Sie können den Rückruf der Zurück-Taste wie folgt verwenden:

- (BOOL) navigationShouldPopOnBackButton
{
    [self backAction];
    return NO;
}

- (void) backAction {
    // your code goes here
    // show confirmation alert, for example
    // ...
}

Für die schnelle Version können Sie so etwas wie im globalen Bereich tun

extension UIViewController {
     @objc func navigationShouldPopOnBackButton() -> Bool {
     return true
    }
}

extension UINavigationController: UINavigationBarDelegate {
     public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
          return self.topViewController?.navigationShouldPopOnBackButton() ?? true
    }
}

Unter einem haben Sie den Viewcontroller eingefügt, in dem Sie die Aktion der Zurück-Schaltfläche steuern möchten:

override func navigationShouldPopOnBackButton() -> Bool {
    self.backAction()//Your action you want to perform.

    return true
}
Pedro Magalhães
quelle
1
Ich weiß nicht, warum jemand abgestimmt hat. Dies scheint bei weitem die beste Antwort zu sein.
Avinash
@Avinash Woher kommt navigationShouldPopOnBackButtondas? Es ist nicht Teil der öffentlichen API.
Elitalon
@elitalon Sorry, das war die halbe Antwort. Ich hatte gedacht, der verbleibende Kontext sei fraglich. Wie auch immer, habe die Antwort jetzt aktualisiert
Avinash
1

Wie Coli88 sagte, sollten Sie das UINavigationBarDelegate-Protokoll überprüfen.

Allgemeiner können Sie auch verwenden - (void)viewWillDisapear:(BOOL)animated, um benutzerdefinierte Arbeiten auszuführen, wenn die vom aktuell sichtbaren Ansichtscontroller beibehaltene Ansicht bald ausgeblendet wird. Leider würde dies die Push- und Pop-Fälle stören.

ramdam
quelle
1

Für Swift mit einem UINavigationController:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    if self.navigationController?.topViewController != self {
        print("back button tapped")
    }
}
Murray Sagal
quelle
1

Die Antwort von 7ynk3r war sehr ähnlich zu dem, was ich am Ende verwendet habe, aber es waren einige Verbesserungen erforderlich:

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {

    UIViewController *topViewController = self.topViewController;
    BOOL wasBackButtonClicked = topViewController.navigationItem == item;

    if (wasBackButtonClicked) {
        if ([topViewController respondsToSelector:@selector(navBackButtonPressed)]) {
            // if user did press back on the view controller where you handle the navBackButtonPressed
            [topViewController performSelector:@selector(navBackButtonPressed)];
            return NO;
        } else {
            // if user did press back but you are not on the view controller that can handle the navBackButtonPressed
            [self popViewControllerAnimated:YES];
            return YES;
        }
    } else {
        // when you call popViewController programmatically you do not want to pop it twice
        return YES;
    }
}
micromanc3r
quelle
0

self.navigationController.isMovingFromParentViewController funktioniert unter iOS8 und 9 nicht mehr. Ich verwende:

-(void) viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    if (self.navigationController.topViewController != self)
    {
        // Is Popping
    }
}
Vassily
quelle
-1

(SCHNELL)

Endlich gefundene Lösung. Die gesuchte Methode ist "willShowViewController", eine delegierte Methode von UINavigationController

//IMPORT UINavigationControllerDelegate !!
class PushedController: UIViewController, UINavigationControllerDelegate {

    override func viewDidLoad() {
        //set delegate to current class (self)
        navigationController?.delegate = self
    }

    func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
        //MyViewController shoud be the name of your parent Class
        if var myViewController = viewController as? MyViewController {
            //YOUR STUFF
        }
    }
}
Jiří Zahálka
quelle
Das Problem bei diesem Ansatz ist , dass es Paare MyViewControllerzu PushedController.
Clozach