Aktion für Zurück-Schaltfläche im Navigations-Controller einstellen

180

Ich versuche, die Standardaktion der Zurück-Schaltfläche in einem Navigationscontroller zu überschreiben. Ich habe einem Ziel eine Aktion auf der benutzerdefinierten Schaltfläche bereitgestellt. Das Seltsame ist, wenn es zugewiesen wird, obwohl das Backbutton-Attribut es ihnen nicht beachtet und es nur die aktuelle Ansicht öffnet und zum Stamm zurückkehrt:

UIBarButtonItem *backButton = [[UIBarButtonItem alloc] 
                                  initWithTitle: @"Servers" 
                                  style:UIBarButtonItemStylePlain 
                                  target:self 
                                  action:@selector(home)];
self.navigationItem.backBarButtonItem = backButton;

Sobald ich es durch das leftBarButtonItemauf eingestellt navigationItemhabe, ruft es meine Aktion auf, aber dann sieht der Knopf wie ein einfacher runder aus, anstatt wie der mit einem Pfeil nach hinten:

self.navigationItem.leftBarButtonItem = backButton;

Wie kann ich meine benutzerdefinierte Aktion aufrufen, bevor ich zur Stammansicht zurückkehre? Gibt es eine Möglichkeit, die Standard-Back-Aktion zu überschreiben, oder gibt es eine Methode, die beim Verlassen einer Ansicht immer aufgerufen wird ( viewDidUnloadfunktioniert das nicht)?

Papageien
quelle
Aktion: @selector (home)]; braucht ein: nach der Auswahlaktion: @selector (home :)]; Andernfalls funktioniert es nicht
PartySoft
7
@PartySoft Das stimmt nur, wenn die Methode mit dem Doppelpunkt deklariert ist. Es ist absolut gültig, dass Schaltflächen Selektoren aufrufen, die keine Parameter annehmen.
mbm29414
3
Warum sollte Apple keinen Knopf mit einem Stil bereitstellen, der wie ein Zurück-Knopf geformt ist? Scheint ziemlich offensichtlich.
JohnK
Schauen Sie sich die Lösung in diesem Thread an
Jiri Volejnik

Antworten:

363

Versuchen Sie, dies in den Ansichts-Controller zu legen, in dem Sie die Presse erkennen möchten:

-(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];
}
William Jockusch
quelle
1
Dies ist eine raffinierte, saubere, nette und sehr gut durchdachte
Problemumgehung
6
+1 großer Hack, bietet aber keine Kontrolle über die Animation von Pop
Matm
3
Funktioniert bei mir nicht, wenn ich über eine Schaltfläche eine Nachricht an den Delegierten sende und der Delegat den Controller öffnet - dies wird immer noch ausgelöst.
SAHM
21
Ein weiteres Problem ist, dass Sie nicht unterscheiden können, ob der Benutzer die Zurück-Taste gedrückt hat oder ob Sie programmgesteuert [self.navigationController popViewControllerAnimated: YES] aufgerufen haben
Chase Roberts
10
Nur eine FYI: Swift-Version:if (find(self.navigationController!.viewControllers as! [UIViewController],self)==nil)
HEADCRASH
177

Ich habe die Erweiterung UIViewController-BackButtonHandler implementiert . Es muss nichts unterklassifiziert werden. Fügen Sie es einfach in Ihr Projekt ein und überschreiben Sie die navigationShouldPopOnBackButtonMethode in der UIViewControllerKlasse:

-(BOOL) navigationShouldPopOnBackButton {
    if(needsShowConfirmation) {
        // Show confirmation alert
        // ...
        return NO; // Ignore 'Back' button this time
    }
    return YES; // Process 'Back' button click and Pop view controler
}

Beispiel-App herunterladen .

onegray
quelle
16
Dies ist die sauberste Lösung, die ich je gesehen habe, besser und einfacher als die Verwendung Ihres eigenen benutzerdefinierten UIButton. Vielen Dank!
Ramirogm
4
Das navigationBar:shouldPopItem:ist keine private Methode, da es Teil des UINavigationBarDelegateProtokolls ist.
Onegray
1
Aber implementiert UINavigationController den Delegaten bereits (um zurückzukehren YES)? oder wird es in Zukunft? Unterklassen sind wahrscheinlich eine sicherere Option
Sam
8
Ich habe dies gerade (übrigens ziemlich cool) in iOS 7.1 implementiert und festgestellt, dass NOdie Zurück-Schaltfläche nach dem Zurückkehren deaktiviert bleibt (visuell, da sie weiterhin Berührungsereignisse empfängt und darauf reagiert). Ich habe es umgangen, indem ich elseder shouldPopPrüfung eine Anweisung hinzugefügt und die Unteransichten der Navigationsleiste durchlaufen habe und den alphaWert in einem Animationsblock bei Bedarf auf 1 zurückgesetzt habe: gist.github.com/idevsoftware/9754057
boliva
2
Dies ist eine der besten Erweiterungen, die ich je gesehen habe. Ich danke dir sehr.
Srikanth
42

Anders als Amagrammer sagte, ist es möglich. Sie müssen Ihre Unterklasse navigationController. Ich habe hier alles erklärt (einschließlich Beispielcode).

HansPinckaers
quelle
In der Dokumentation von Apple ( developer.apple.com/iphone/library/documentation/UIKit/… ) heißt es: "Diese Klasse ist nicht für Unterklassen vorgesehen." Obwohl ich nicht sicher bin, was sie damit meinen - sie könnten bedeuten "Sie sollten das normalerweise nicht tun müssen" oder sie könnten bedeuten "wir werden Ihre App ablehnen, wenn Sie sich mit unserem Controller
anlegen
Dies ist sicherlich der einzige Weg, dies zu tun. Ich wünschte, ich könnte dir mehr Punkte geben, Hans!
Adam Eberbach
1
Können Sie mit dieser Methode tatsächlich verhindern, dass eine Ansicht beendet wird? Was würde die popViewControllerAnimated-Methode zurückgeben, wenn die Ansicht nicht beendet werden soll?
JosephH
1
Ja du kannst. Rufen Sie in Ihrer Implementierung nur nicht die Superclass-Methode auf, seien Sie sich dessen bewusst! Sie sollten das nicht tun, der Benutzer erwartet, dass er wieder in die Navigation zurückkehrt. Sie können eine Bestätigung anfordern. Laut Apples-Dokumentation gibt popViewController Folgendes zurück: "Der View-Controller, der vom Stapel entfernt wurde." Wenn also nichts auftaucht, sollten Sie null zurückgeben.
HansPinckaers
1
@HansPickaers Ich denke, Ihre Antwort zum Verhindern, dass eine Ansicht beendet wird, ist möglicherweise etwas falsch. Wenn ich eine Bestätigungsmeldung aus der Unterklassenimplementierung von popViewControllerAnimated: anzeige, animiert die Navigationsleiste immer noch eine Ebene im Baum, unabhängig davon, was ich zurückgebe. Dies scheint darauf zurückzuführen zu sein, dass durch Klicken auf die Schaltfläche "Zurück" das Befehl "PopNavigationItem" in der Navigationsleiste aufgerufen wird. Ich kehre null von meiner Unterklassenmethode zurück, wie empfohlen.
Deepwinter
15

Schnelle Version:

(von https://stackoverflow.com/a/19132881/826435 )

In Ihrem View Controller passen Sie sich einfach einem Protokoll an und führen alle erforderlichen Aktionen aus:

extension MyViewController: NavigationControllerBackButtonDelegate {
    func shouldPopOnBackButtonPress() -> Bool {
        performSomeActionOnThePressOfABackButton()
        return false
    }
}

Erstellen Sie dann beispielsweise eine Klasse NavigationController+BackButtonund kopieren Sie einfach den folgenden Code:

protocol NavigationControllerBackButtonDelegate {
    func shouldPopOnBackButtonPress() -> Bool
}

extension UINavigationController {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        // Prevents from a synchronization issue of popping too many navigation items
        // and not enough view controllers or viceversa from unusual tapping
        if viewControllers.count < navigationBar.items!.count {
            return true
        }

        // Check if we have a view controller that wants to respond to being popped
        var shouldPop = true
        if let viewController = topViewController as? NavigationControllerBackButtonDelegate {
            shouldPop = viewController.shouldPopOnBackButtonPress()
        }

        if (shouldPop) {
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        } else {
            // Prevent the back button from staying in an disabled state
            for view in navigationBar.subviews {
                if view.alpha < 1.0 {
                    UIView.animate(withDuration: 0.25, animations: {
                        view.alpha = 1.0
                    })
                }
            }

        }

        return false
    }
}
kgaidis
quelle
Vielleicht habe ich etwas verpasst, aber es funktioniert nicht für mich, die Methode performSomeActionOnThePressOfABackButton der Erweiterung wird nie aufgerufen
Turvy
@FlorentBreton vielleicht ein Missverständnis? shouldPopOnBackButtonPresssollte aufgerufen werden, solange es keine Bugs gibt. performSomeActionOnThePressOfABackButtonist nur eine erfundene Methode, die es nicht gibt.
kgaidis
Ich habe es verstanden, deshalb habe ich performSomeActionOnThePressOfABackButtonin meinem Controller eine Methode erstellt , um eine bestimmte Aktion auszuführen, wenn die Zurück-Taste gedrückt wird, aber diese Methode wurde nie aufgerufen. Die Aktion ist eine normale Rückgabe
Turvy
1
Ich arbeite auch nicht für mich. Die shouldPop-Methode wird niemals aufgerufen. Haben Sie irgendwo einen Delegierten eingestellt?
Tim Autin
@ TimAutin Ich habe das gerade noch einmal getestet und es scheint, als hätte sich etwas geändert. Key Teil zu verstehen , dass in ein UINavigationController, die navigationBar.delegatean die Navigationssteuerung eingestellt ist. Also sollten die Methoden aufgerufen werden. In Swift kann ich sie jedoch nicht dazu bringen, selbst in einer Unterklasse aufgerufen zu werden. Ich habe sie jedoch dazu gebracht, in Objective-C aufgerufen zu werden, daher würde ich vorerst nur die Objective-C-Version verwenden. Könnte ein schneller Fehler sein.
kgaidis
5

Es ist nicht möglich, direkt zu tun. Es gibt ein paar Alternativen:

  1. Erstellen Sie Ihre eigene Benutzerdefinierung UIBarButtonItem, die beim Tippen validiert und angezeigt wird, wenn der Test erfolgreich ist
  2. Überprüfen Sie den Inhalt des Formularfelds mit einer UITextFieldDelegatmethode, z. B. -textFieldShouldReturn:die aufgerufen wird, nachdem die Taste Returnoder Doneauf der Tastatur gedrückt wurde

Der Nachteil der ersten Option ist, dass auf den nach links zeigenden Pfeil der Zurück-Schaltfläche nicht über eine benutzerdefinierte Leistenschaltfläche zugegriffen werden kann. Sie müssen also ein Bild verwenden oder eine normale Stilschaltfläche verwenden.

Die zweite Option ist hilfreich, da Sie das Textfeld in der Delegate-Methode zurückerhalten, sodass Sie Ihre Validierungslogik auf das spezifische Textfeld ausrichten können, das an die Delegate-Rückrufmethode gesendet wird.

Alex Reynolds
quelle
5

Aus einigen Threading-Gründen war die von @HansPinckaers erwähnte Lösung nicht richtig für mich, aber ich habe einen sehr einfacheren Weg gefunden, den Touch-Button zu berühren, und ich möchte dies hier festhalten, falls dies stundenlange Täuschungen vermeiden könnte jemand anderes. Der Trick ist wirklich einfach: Fügen Sie einfach einen transparenten UIButton als Unteransicht zu Ihrer UINavigationBar hinzu und stellen Sie Ihre Selektoren für ihn so ein, als wäre es die echte Schaltfläche! Hier ist ein Beispiel mit Monotouch und C #, aber die Übersetzung in Objective-C sollte nicht zu schwer zu finden sein.

public class Test : UIViewController {
    public override void ViewDidLoad() {
        UIButton b = new UIButton(new RectangleF(0, 0, 60, 44)); //width must be adapted to label contained in button
        b.BackgroundColor = UIColor.Clear; //making the background invisible
        b.Title = string.Empty; // and no need to write anything
        b.TouchDown += delegate {
            Console.WriteLine("caught!");
            if (true) // check what you want here
                NavigationController.PopViewControllerAnimated(true); // and then we pop if we want
        };
        NavigationController.NavigationBar.AddSubview(button); // insert the button to the nav bar
    }
}

Unterhaltsame Tatsache: Zu Testzwecken und um gute Abmessungen für meinen gefälschten Knopf zu finden, habe ich seine Hintergrundfarbe auf Blau gesetzt ... Und sie wird hinter dem Zurück-Knopf angezeigt! Wie auch immer, es fängt immer noch jede Berührung auf, die auf die ursprüngliche Taste abzielt.

Psycho
quelle
3

Mit dieser Technik können Sie den Text der Schaltfläche "Zurück" ändern, ohne den Titel eines der Ansichts-Controller zu beeinflussen oder zu sehen, wie sich der Text der Schaltfläche "Zurück" während der Animation ändert.

Fügen Sie dies der init-Methode im aufrufenden View-Controller hinzu:

UIBarButtonItem *temporaryBarButtonItem = [[UIBarButtonItem alloc] init];   
temporaryBarButtonItem.title = @"Back";
self.navigationItem.backBarButtonItem = temporaryBarButtonItem;
[temporaryBarButtonItem release];
Jason Moore
quelle
3

Einfachster Weg

Sie können die Delegatmethoden des UINavigationController verwenden. Die Methode willShowViewControllerwird aufgerufen, wenn die Zurück-Taste Ihres VC gedrückt wird. Tun Sie, was Sie wollen, wenn Sie die Zurück-Taste drücken

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
Zar E Ahmer
quelle
Stellen Sie sicher, dass sich Ihr View Controller als Delegat des geerbten Navigationscontrollers festlegt und dem UINavigationControllerDelegate-Protokoll entspricht
Justin Milo
3

Hier ist meine Swift-Lösung. Überschreiben Sie in Ihrer Unterklasse von UIViewController die Methode navigationShouldPopOnBackButton.

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

extension UINavigationController {

    func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool {
        if let vc = self.topViewController {
            if vc.navigationShouldPopOnBackButton() {
                self.popViewControllerAnimated(true)
            } else {
                for it in navigationBar.subviews {
                    let view = it as! UIView
                    if view.alpha < 1.0 {
                        [UIView .animateWithDuration(0.25, animations: { () -> Void in
                            view.alpha = 1.0
                        })]
                    }
                }
                return false
            }
        }
        return true
    }

}
AutomatonTec
quelle
Überschreiben der Methode navigationShouldPopOnBackButton in UIViewController funktioniert nicht - Das Programm führt die übergeordnete Methode aus, nicht die überschriebene. Irgendeine Lösung dafür? Hat jemand das gleiche Problem?
Pawel Cala
es geht den ganzen Weg zurück zu Rootview, wenn return true
Pawriwes
@ Pawriwes Hier ist eine Lösung, die ich geschrieben habe und die für mich zu funktionieren scheint: stackoverflow.com/a/34343418/826435
kgaidis
3

Es wurde eine Lösung gefunden, die auch den Stil der Zurück-Schaltfläche beibehält. Fügen Sie Ihrem View Controller die folgende Methode hinzu.

-(void) overrideBack{

    UIButton *transparentButton = [[UIButton alloc] init];
    [transparentButton setFrame:CGRectMake(0,0, 50, 40)];
    [transparentButton setBackgroundColor:[UIColor clearColor]];
    [transparentButton addTarget:self action:@selector(backAction:) forControlEvents:UIControlEventTouchUpInside];
    [self.navigationController.navigationBar addSubview:transparentButton];


}

Stellen Sie nun eine Funktion bereit, die für die folgende Methode erforderlich ist:

-(void)backAction:(UIBarButtonItem *)sender {
    //Your functionality
}

Alles was es tut ist den hinteren Knopf mit einem transparenten Knopf abzudecken;)

Sarasranglt
quelle
3

Überschreiben der Navigationsleiste (_ navigationBar: shouldPop) : Dies ist keine gute Idee, auch wenn sie funktioniert. Für mich verursachte es zufällige Abstürze beim Zurücknavigieren. Ich empfehle Ihnen, die Schaltfläche "Zurück" einfach zu überschreiben, indem Sie die Standard-Schaltfläche "Zurück" aus dem Navigationselement entfernen und eine benutzerdefinierte Schaltfläche "Zurück" wie folgt erstellen:

override func viewDidLoad(){
   super.viewDidLoad()
   
   navigationItem.leftBarButton = .init(title: "Go Back", ... , action: #selector(myCutsomBackAction) 

   ...
 
}

=======================================

Aufbauend auf früheren Antworten mit UIAlert in Swift5 auf asynchrone Weise


protocol NavigationControllerBackButtonDelegate {
    func shouldPopOnBackButtonPress(_ completion: @escaping (Bool) -> ())
}

extension UINavigationController: UINavigationBarDelegate {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
      
        if viewControllers.count < navigationBar.items!.count {
            return true
        }
        
        // Check if we have a view controller that wants to respond to being popped
        
        if let viewController = topViewController as? NavigationControllerBackButtonDelegate {
            
            viewController.shouldPopOnBackButtonPress { shouldPop in
                if (shouldPop) {
                    /// on confirm => pop
                    DispatchQueue.main.async {
                        self.popViewController(animated: true)
                    }
                } else {
                    /// on cancel => do nothing
                }
            }
            /// return false => so navigator will cancel the popBack
            /// until user confirm or cancel
            return false
        }else{
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        }
        return true
    }
}

Auf Ihrem Controller


extension MyController: NavigationControllerBackButtonDelegate {
    
    func shouldPopOnBackButtonPress(_ completion: @escaping (Bool) -> ()) {
    
        let msg = "message"
        
        /// show UIAlert
        alertAttention(msg: msg, actions: [
            
            .init(title: "Continuer", style: .destructive, handler: { _ in
                completion(true)
            }),
            .init(title: "Annuler", style: .cancel, handler: { _ in
                completion(false)
            })
            ])
   
    }

}
Brahimm
quelle
Können Sie bitte angeben, was mit der Prüfung if viewControllers.count <navigationBar.items! .Count {return true} los ist?
H4Hugo
// Verhindert ein Synchronisationsproblem, bei dem zu viele Navigationselemente // und nicht genügend View Controller oder umgekehrt ungewöhnliches Tippen
angezeigt werden
2

Ich glaube nicht, dass dies leicht möglich ist. Ich glaube, der einzige Weg, dies zu umgehen, besteht darin, ein eigenes Pfeilbild mit der Zurück-Taste zu erstellen, um es dort oben zu platzieren. Anfangs war es für mich frustrierend, aber ich verstehe, warum es aus Gründen der Konsistenz weggelassen wurde.

Sie können schließen (ohne den Pfeil), indem Sie eine reguläre Schaltfläche erstellen und die Standard-Zurück-Schaltfläche ausblenden:

self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:@"Servers" style:UIBarButtonItemStyleDone target:nil action:nil] autorelease];
self.navigationItem.hidesBackButton = YES;
Meltemi
quelle
2
Ja, das Problem ist, dass es wie der normale Zurück-Button aussehen soll. Ich brauche es nur, um zuerst meine benutzerdefinierte Aktion aufzurufen ...
Papageien
2

Es gibt einen einfacheren Weg, indem Sie einfach die Delegate-Methode von unterordnenUINavigationBar und die Methode überschreiben .ShouldPopItem

jazzyjef2002
quelle
Ich denke, Sie wollen sagen, Unterklasse der UINavigationController-Klasse und implementieren eine shouldPopItem-Methode. Das funktioniert gut für mich. Diese Methode sollte jedoch nicht einfach JA oder NEIN zurückgeben, wie Sie es erwarten würden. Eine Erklärung und Lösung finden Sie hier: stackoverflow.com/a/7453933/462162
arlomedia
2

Die Lösung von onegray ist nicht sicher. Laut den offiziellen Dokumenten von Apple, https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html , sollten wir dies vermeiden.

"Wenn der Name einer in einer Kategorie deklarierten Methode mit einer Methode in der ursprünglichen Klasse oder einer Methode in einer anderen Kategorie in derselben Klasse (oder sogar einer Oberklasse) identisch ist, ist das Verhalten hinsichtlich der verwendeten Methodenimplementierung undefiniert zur Laufzeit. Dies ist weniger wahrscheinlich, wenn Sie Kategorien mit Ihren eigenen Klassen verwenden, kann jedoch Probleme verursachen, wenn Sie Kategorien verwenden, um Methoden zu Standardklassen von Cocoa oder Cocoa Touch hinzuzufügen. "

user2612791
quelle
2

Verwenden von Swift:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    if self.navigationController?.topViewController != self {
        print("back button tapped")
    }
}
Murray Sagal
quelle
Ab iOS 10 und möglicherweise früher funktioniert dies nicht mehr.
Murray Sagal
2

Hier ist die Swift 3-Version von @oneways Antwort zum Abfangen des Ereignisses der Zurück-Schaltfläche der Navigationsleiste, bevor es ausgelöst wird. Da UINavigationBarDelegatedies nicht verwendet werden kann UIViewController, müssen Sie einen Delegaten erstellen, der beim navigationBar shouldPopAufruf ausgelöst wird.

@objc public protocol BackButtonDelegate {
      @objc optional func navigationShouldPopOnBackButton() -> Bool 
}

extension UINavigationController: UINavigationBarDelegate  {

    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {

        if viewControllers.count < (navigationBar.items?.count)! {                
            return true
        }

        var shouldPop = true
        let vc = self.topViewController

        if vc.responds(to: #selector(vc.navigationShouldPopOnBackButton)) {
            shouldPop = vc.navigationShouldPopOnBackButton()
        }

        if shouldPop {
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        } else {
            for subView in navigationBar.subviews {
                if(0 < subView.alpha && subView.alpha < 1) {
                    UIView.animate(withDuration: 0.25, animations: {
                        subView.alpha = 1
                    })
                }
            }
        }

        return false
    }
}

Fügen Sie dann in Ihrem Ansichts-Controller die Delegatenfunktion hinzu:

class BaseVC: UIViewController, BackButtonDelegate {
    func navigationShouldPopOnBackButton() -> Bool {
        if ... {
            return true
        } else {
            return false
        }        
    }
}

Ich habe festgestellt, dass wir häufig einen Alarm-Controller hinzufügen möchten, damit Benutzer entscheiden können, ob sie zurückkehren möchten. Wenn ja, können Sie immer return falsein navigationShouldPopOnBackButton()Funktion und schließen Sie die View - Controller durch so etwas wie dies zu tun:

func navigationShouldPopOnBackButton() -> Bool {
     let alert = UIAlertController(title: "Warning",
                                          message: "Do you want to quit?",
                                          preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { UIAlertAction in self.yes()}))
            alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: { UIAlertAction in self.no()}))
            present(alert, animated: true, completion: nil)
      return false
}

func yes() {
     print("yes")
     DispatchQueue.main.async {
            _ = self.navigationController?.popViewController(animated: true)
        }
}

func no() {
    print("no")       
}
Lawliet
quelle
Ich erhalte eine Fehlermeldung: Value of type 'UIViewController' has no member 'navigationShouldPopOnBackButton' Wenn ich versuche, Ihren Code für die Zeile zu kompilieren, wird if vc.responds(to: #selector(v...auch self.topViewControllereine Option zurückgegeben, und es gibt auch eine Warnung dafür.
Sankar
FWIW, ich habe diesen Code behoben, indem ich Folgendes gemacht habe: let vc = self.topViewController as! MyViewControllerund es scheint bisher gut zu funktionieren. Wenn Sie der Meinung sind, dass dies eine richtige Änderung ist, können Sie den Code bearbeiten. Wenn Sie der Meinung sind, dass dies nicht getan werden sollte, bin ich froh zu wissen, warum. Danke für diesen Code. Sie sollten wahrscheinlich einen Blog-Beitrag darüber schreiben, da diese Antwort gemäß den Stimmen vergraben ist.
Sankar
@SankarP Der Grund, warum Sie diesen Fehler erhalten haben, ist, dass Sie MyViewControllermöglicherweise nicht konform sind BackButtonDelegate. Anstatt das Auspacken zu erzwingen, sollten Sie dies tun guard let vc = self.topViewController as? MyViewController else { return true }, um einen möglichen Absturz zu vermeiden.
Lawliet
Vielen Dank. Ich denke, die Guard-Anweisung sollte lauten: guard let vc = self.topViewController as? MyViewController else { self.popViewController(animated: true) return true }um sicherzustellen, dass der Bildschirm auf die richtige Seite verschoben wird, falls er nicht richtig besetzt werden kann. Ich verstehe jetzt, dass die navigationBarFunktion in allen VCs aufgerufen wird und nicht nur im Viewcontroller, in dem dieser Code vorhanden ist. Vielleicht ist es auch gut, den Code in Ihrer Antwort zu aktualisieren? Vielen Dank.
Sankar
2

Swift 4 iOS 11.3 Version:

Dies baut auf der Antwort von kgaidis von https://stackoverflow.com/a/34343418/4316579 auf

Ich bin nicht sicher, wann die Erweiterung nicht mehr funktioniert, aber zum Zeitpunkt dieses Schreibens (Swift 4) scheint es, dass die Erweiterung nicht mehr ausgeführt wird, es sei denn, Sie erklären die UINavigationBarDelegate-Konformität wie unten beschrieben.

Ich hoffe, dies hilft Menschen, die sich fragen, warum ihre Erweiterung nicht mehr funktioniert.

extension UINavigationController: UINavigationBarDelegate {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {

    }
}
Edward L.
quelle
1

Wenn Sie die Ziel- und Aktionsvariablen verwenden, die Sie derzeit 'nil' verlassen, sollten Sie in der Lage sein, Ihre Speicherdialoge so zu verkabeln, dass sie aufgerufen werden, wenn die Schaltfläche "ausgewählt" ist. Achtung, dies kann in seltsamen Momenten ausgelöst werden.

Ich stimme größtenteils mit Amagrammer überein, aber ich denke nicht, dass es so schwierig wäre, die Schaltfläche mit dem Pfeil benutzerdefiniert zu machen. Ich würde einfach die Zurück-Schaltfläche umbenennen, einen Screenshot machen, die benötigte Schaltflächengröße in Photoshop bearbeiten und das Bild oben auf Ihrer Schaltfläche haben.

TahoeWolverine
quelle
Ich bin damit einverstanden, dass Sie Photoshop verwenden können, und ich denke, ich könnte dies tun, wenn ich es wirklich wollte, aber jetzt habe ich beschlossen, das Erscheinungsbild ein wenig zu ändern, damit dies so funktioniert, wie ich es möchte.
John Ballinger
Ja, außer dass die Aktionen nicht ausgelöst werden, wenn sie an das backBarButtonItem angehängt werden. Ich weiß nicht, ob dies ein Fehler oder eine Funktion ist. Es ist möglich, dass selbst Apple es nicht weiß. Auch bei der Photoshopping-Übung wäre ich vorsichtig, wenn Apple die App wegen Missbrauchs eines kanonischen Symbols ablehnen würde.
Amagrammer
Heads-up: Diese Antwort wurde aus einem Duplikat zusammengeführt.
Shog9
1

Sie können versuchen, auf das Element "NavigationBars Right Button" zuzugreifen und dessen Selector-Eigenschaft festzulegen. Hier ist eine Referenz- UIBarButtonItem-Referenz . Wenn dies nicht funktioniert, setzen Sie das rechte Schaltflächenelement der Navigationsleiste auf ein benutzerdefiniertes UIBarButtonItem, das Sie verwenden erstelle und setze seinen Selektor ... hoffe das hilft

Daniel
quelle
Heads-up: Diese Antwort wurde aus einem Duplikat zusammengeführt.
Shog9
1

Für ein Formular, für das Benutzereingaben wie diese erforderlich sind, würde ich empfehlen, es als "modal" anstelle eines Teils Ihres Navigationsstapels aufzurufen. Auf diese Weise müssen sie sich um das Geschäft auf dem Formular kümmern, dann können Sie es validieren und über eine benutzerdefinierte Schaltfläche schließen. Sie können sogar eine Navigationsleiste entwerfen, die genauso aussieht wie der Rest Ihrer App, Ihnen jedoch mehr Kontrolle bietet.

Travis M.
quelle
Heads-up: Diese Antwort wurde aus einem Duplikat zusammengeführt.
Shog9
1

Um die Schaltfläche Zurück abzufangen, bedecken Sie sie einfach mit einem transparenten UIControl und fangen Sie die Berührungen ab.

@interface MyViewController : UIViewController
{
    UIControl   *backCover;
    BOOL        inhibitBackButtonBOOL;
}
@end

@implementation MyViewController
-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    // Cover the back button (cannot do this in viewWillAppear -- too soon)
    if ( backCover == nil ) {
        backCover = [[UIControl alloc] initWithFrame:CGRectMake( 0, 0, 80, 44)];
#if TARGET_IPHONE_SIMULATOR
        // show the cover for testing
        backCover.backgroundColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.15];
#endif
        [backCover addTarget:self action:@selector(backCoverAction) forControlEvents:UIControlEventTouchDown];
        UINavigationBar *navBar = self.navigationController.navigationBar;
        [navBar addSubview:backCover];
    }
}

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

    [backCover removeFromSuperview];
    backCover = nil;
}

- (void)backCoverAction
{
    if ( inhibitBackButtonBOOL ) {
        NSLog(@"Back button aborted");
        // notify the user why...
    } else {
        [self.navigationController popViewControllerAnimated:YES]; // "Back"
    }
}
@end
Jeff
quelle
Heads-up: Diese Antwort wurde aus einem Duplikat zusammengeführt.
Shog9
1

Zumindest in Xcode 5 gibt es eine einfache und ziemlich gute (nicht perfekte) Lösung. Ziehen Sie in IB ein Balkenschaltflächenelement aus dem Bereich "Dienstprogramme" und legen Sie es auf der linken Seite der Navigationsleiste ab, wo sich die Schaltfläche "Zurück" befinden würde. Setzen Sie das Etikett auf "Zurück". Sie haben eine funktionierende Schaltfläche, die Sie mit Ihrer IBAction verknüpfen und Ihren viewController schließen können. Ich mache etwas Arbeit und löse dann einen Abwicklungssegment aus und es funktioniert perfekt.

Was nicht ideal ist, ist, dass diese Schaltfläche nicht den Pfeil <erhält und den vorherigen VCs-Titel nicht weitergibt, aber ich denke, dies kann verwaltet werden. Für meine Zwecke habe ich die neue Schaltfläche "Zurück" als "Fertig" festgelegt, damit der Zweck klar ist.

Sie haben auch zwei Zurück-Schaltflächen im IB-Navigator, die Sie jedoch aus Gründen der Übersichtlichkeit leicht beschriften können.

Geben Sie hier die Bildbeschreibung ein

Dan Loughney
quelle
1

Schnell

override func viewWillDisappear(animated: Bool) {
    let viewControllers = self.navigationController?.viewControllers!
    if indexOfArray(viewControllers!, searchObject: self) == nil {
        // do something
    }
    super.viewWillDisappear(animated)
}

func indexOfArray(array:[AnyObject], searchObject: AnyObject)-> Int? {
    for (index, value) in enumerate(array) {
        if value as UIViewController == searchObject as UIViewController {
            return index
        }
    }
    return nil
}
zono
quelle
1

Dieser Ansatz hat bei mir funktioniert (aber die Schaltfläche "Zurück" hat nicht das Zeichen "<"):

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIBarButtonItem* backNavButton = [[UIBarButtonItem alloc] initWithTitle:@"Back"
                                                                      style:UIBarButtonItemStyleBordered
                                                                     target:self
                                                                     action:@selector(backButtonClicked)];
    self.navigationItem.leftBarButtonItem = backNavButton;
}

-(void)backButtonClicked
{
    // Do something...
    AppDelegate* delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
    [delegate.navController popViewControllerAnimated:YES];
}
Ivan
quelle
1

Schnelle Version von @ onegrays Antwort

protocol RequestsNavigationPopVerification {
    var confirmationTitle: String { get }
    var confirmationMessage: String { get }
}

extension RequestsNavigationPopVerification where Self: UIViewController {
    var confirmationTitle: String {
        return "Go back?"
    }

    var confirmationMessage: String {
        return "Are you sure?"
    }
}

final class NavigationController: UINavigationController {

    func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool {

        guard let requestsPopConfirm = topViewController as? RequestsNavigationPopVerification else {
            popViewControllerAnimated(true)
            return true
        }

        let alertController = UIAlertController(title: requestsPopConfirm.confirmationTitle, message: requestsPopConfirm.confirmationMessage, preferredStyle: .Alert)

        alertController.addAction(UIAlertAction(title: "Cancel", style: .Cancel) { _ in
            dispatch_async(dispatch_get_main_queue(), {
                let dimmed = navigationBar.subviews.flatMap { $0.alpha < 1 ? $0 : nil }
                UIView.animateWithDuration(0.25) {
                    dimmed.forEach { $0.alpha = 1 }
                }
            })
            return
        })

        alertController.addAction(UIAlertAction(title: "Go back", style: .Default) { _ in
            dispatch_async(dispatch_get_main_queue(), {
                self.popViewControllerAnimated(true)
            })
        })

        presentViewController(alertController, animated: true, completion: nil)

        return false
    }
}

Passen Sie sich jetzt in jedem Controller einfach an RequestsNavigationPopVerificationund dieses Verhalten wird standardmäßig übernommen.

Adam Waite
quelle
1

Verwenden isMovingFromParentViewController

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(true)

    if self.isMovingFromParentViewController {
        // current viewController is removed from parent
        // do some work
    }
}
herrk
quelle
Können Sie weiter erklären, wie dies beweist, dass die Zurück-Taste gedrückt wurde?
Murray Sagal
Dies ist einfach, funktioniert jedoch nur, wenn Sie sicher sind, dass Sie aus einer untergeordneten Ansicht, die Sie laden, zu dieser Ansicht zurückkehren. Wenn das Kind diese Ansicht überspringt, während es zum übergeordneten Element zurückkehrt, wird Ihr Code nicht aufgerufen (die Ansicht war bereits verschwunden, ohne vom übergeordneten Element verschoben worden zu sein). Dies ist jedoch das gleiche Problem, da nur Ereignisse beim Auslösen der Schaltfläche "Zurück" behandelt werden, wie vom OP gefordert. Das ist also eine einfache Antwort auf seine Frage.
CMont
Das ist super einfach und elegant. Ich liebe es. Nur ein Problem: Dies wird auch ausgelöst, wenn der Benutzer wischt, um zurückzukehren, selbst wenn er auf halbem Weg abbricht. Vielleicht wäre eine bessere Lösung, diesen Code einzufügen viewDidDisappear. Auf diese Weise wird es nur ausgelöst, wenn die Ansicht definitiv verschwunden ist.
Phontaine Judd
1

Die Antwort von @William ist jedoch richtig. Wenn der Benutzer eine Swipe-to-Go-Back-Geste startet, wird die viewWillDisappearMethode aufgerufen und befindet selfsich nicht im Navigationsstapel ( dh self.navigationController.viewControllersnicht enthalten self), selbst wenn der Swipe ausgeführt wird ist nicht abgeschlossen und der Ansichts-Controller ist nicht tatsächlich geöffnet. Die Lösung wäre also:

  1. Deaktivieren Sie die Wisch-zu-Zurück-Geste in viewDidAppearund erlauben Sie nur die Verwendung der Zurück-Taste, indem Sie Folgendes verwenden:

    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
    }
  2. Oder verwenden Sie viewDidDisappearstattdessen einfach wie folgt:

    - (void)viewDidDisappear:(BOOL)animated
    {
        [super viewDidDisappear:animated];
        if (![self.navigationController.viewControllers containsObject:self])
        {
            // back button was pressed or the the swipe-to-go-back gesture was
            // completed. We know this is true because self is no longer
            // in the navigation stack.
        }
    }
Boherna
quelle
0

Die Lösung, die ich bisher gefunden habe, ist nicht sehr schön, aber sie funktioniert für mich. Mit dieser Antwort ü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];
Ferran Maylinch
quelle
0

Neue Möglichkeit gefunden:

Ziel c

- (void)didMoveToParentViewController:(UIViewController *)parent{
    if (parent == NULL) {
        NSLog(@"Back Pressed");
    }
}

Schnell

override func didMoveToParentViewController(parent: UIViewController?) {
    if parent == nil {
        println("Back Pressed")
    }
}
Ashish Kakkad
quelle