Ich habe einen Blick auf den Navigationscontroller geschoben und wenn ich die Zurück-Taste drücke, wird automatisch die vorherige Ansicht angezeigt. Ich möchte ein paar Dinge tun, wenn die Zurück-Taste gedrückt wird, bevor die Ansicht vom Stapel genommen wird. Welches ist die Rückruffunktion der Rücktaste?
William Jockuschs Antwort Löse dieses Problem mit einfachen Tricks.
-(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];
}
Meiner Meinung nach die beste Lösung.
- (void)didMoveToParentViewController:(UIViewController *)parent
{
if (![parent isEqual:self.parentViewController]) {
NSLog(@"Back pressed");
}
}
Aber es funktioniert nur mit iOS5 +
es ist wahrscheinlich besser, die Zurück-Schaltfläche zu überschreiben, damit Sie das Ereignis bearbeiten können, bevor die Ansicht für Dinge wie die Bestätigung des Benutzers geöffnet wird.
erstellen Sie in viewDidLoad ein UIBarButtonItem und legen Sie self.navigationItem.leftBarButtonItem fest, indem Sie ein sel übergeben
- (void) viewDidLoad
{
// change the back button to cancel and add an event handler
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:@”back”
style:UIBarButtonItemStyleBordered
target:self
action:@selector(handleBack:)];
self.navigationItem.leftBarButtonItem = backButton;
[backButton release];
}
- (void) handleBack:(id)sender
{
// pop to root view controller
[self.navigationController popToRootViewControllerAnimated:YES];
}
Dann können Sie beispielsweise UIAlertView aufrufen, um die Aktion zu bestätigen, und dann den View-Controller anzeigen, usw.
Anstatt eine neue Rücktaste zu erstellen, können Sie die UINavigationController-Delegatmethode verwenden, um Aktionen auszuführen, wenn die Zurück-Taste gedrückt wird.
Ich ende mit diesen Lösungen. Wenn wir auf die Schaltfläche "Zurück" tippen, wird die Methode viewDidDisappear aufgerufen. Wir können überprüfen, indem Sie den isMovingFromParentViewController-Selektor aufrufen, der true zurückgibt. Wir können Daten zurückgeben (Using Delegate).
-(void)viewDidDisappear:(BOOL)animated{
if (self.isMovingToParentViewController) {
}
if (self.isMovingFromParentViewController) {
//moving back
//pass to viewCollection delegate and update UI
[self.delegateObject passBackSavedData:self.dataModel];
}
}
Für "VOR dem Öffnen der Ansicht vom Stapel":
- (void)willMoveToParentViewController:(UIViewController *)parent{
if (parent == nil){
NSLog(@"do whatever you want here");
}
}
Dies ist der richtige Weg, dies zu erkennen.
- (void)willMoveToParentViewController:(UIViewController *)parent{
if (parent == nil){
//do stuff
}
}
diese Methode wird aufgerufen, wenn auch die Ansicht verschoben wird. Das Überprüfen von parent == nil dient dazu, den View-Controller vom Stack zu entfernen
Es gibt einen angemesseneren Weg, als die viewControllers zu fragen. Sie können aus Ihrem Controller einen Delegaten der Navigationsleiste mit der Zurück-Schaltfläche machen. Hier ist ein Beispiel. Weisen Sie in der Implementierung des Controllers, bei dem Sie das Drücken der Zurück-Taste ausführen möchten, an, dass das UINavigationBarDelegate-Protokoll implementiert wird:
@interface MyViewController () <UINavigationBarDelegate>
Dann irgendwo in Ihrem Initialisierungscode (wahrscheinlich in viewDidLoad) machen Sie Ihren Controller zum Delegaten seiner Navigationsleiste:
self.navigationController.navigationBar.delegate = self;
Implementieren Sie schließlich die shouldPopItem-Methode. Diese Methode wird direkt aufgerufen, wenn die Zurück-Taste gedrückt wird. Wenn Sie mehrere Controller oder Navigationselemente im Stack haben, möchten Sie wahrscheinlich prüfen, welches dieser Navigationselemente (der Parameter item) ausgepuffert wird, so dass Sie nur Ihre eigenen Aufgaben erledigen können, wenn Sie dies erwarten. Hier ist ein Beispiel:
-(BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
NSLog(@"Back button got pressed!");
//if you return NO, the back button press is cancelled
return YES;
}
Hier ist eine andere Möglichkeit, die ich implementiert habe (habe es nicht mit einem Abwicklungssegment getestet, aber es würde wahrscheinlich nicht differenzieren, wie andere in Bezug auf andere Lösungen auf dieser Seite angegeben haben), damit der übergeordnete Ansichtscontroller Aktionen vor dem untergeordneten Element ausführt VC it push wird aus dem Ansichtsstapel entfernt (ich habe dies ein paar Ebenen niedriger als der ursprüngliche UINavigationController verwendet.) Dies kann auch verwendet werden, um Aktionen auszuführen, bevor childVC gepusht wird. Dies hat den zusätzlichen Vorteil anstatt ein benutzerdefiniertes UIBarButtonItem oder UIButton zu erstellen.
Lassen Sie Ihre Eltern VC das UINavigationControllerDelegate
-Protokoll übernehmen und sich für delegierte Nachrichten registrieren:
MyParentViewController : UIViewController <UINavigationControllerDelegate>
-(void)viewDidLoad {
self.navigationcontroller.delegate = self;
}
Implementieren Sie diese UINavigationControllerDelegate
-Instanzmethode in MyParentViewController
:
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
// Test if operation is a pop; can also test for a Push (i.e., do something before the ChildVC is pushed
if (operation == UINavigationControllerOperationPop) {
// Make sure it's the child class you're looking for
if ([fromVC isKindOfClass:[ChildViewController class]]) {
// Can handle logic here or send to another method; can also access all properties of child VC at this time
return [self didPressBackButtonOnChildViewControllerVC:fromVC];
}
}
// If you don't want to specify a nav controller transition
return nil;
}
Wenn Sie eine bestimmte Rückruffunktion in der obigen Instanzmethode UINavigationControllerDelegate
angeben
-(id <UIViewControllerAnimatedTransitioning>)didPressBackButtonOnAddSearchRegionsVC:(UIViewController *)fromVC {
ChildViewController *childVC = ChildViewController.new;
childVC = (ChildViewController *)fromVC;
// childVC.propertiesIWantToAccess go here
// If you don't want to specify a nav controller transition
return nil;
}
Wenn Sie "viewWillDisappear" oder eine ähnliche Methode nicht verwenden können, versuchen Sie, UINavigationController zu subclass. Dies ist die Kopfklasse:
#import <Foundation/Foundation.h>
@class MyViewController;
@interface CCNavigationController : UINavigationController
@property (nonatomic, strong) MyViewController *viewController;
@end
Implementierungsklasse:
#import "CCNavigationController.h"
#import "MyViewController.h"
@implementation CCNavigationController {
}
- (UIViewController *)popViewControllerAnimated:(BOOL)animated {
@"This is the moment for you to do whatever you want"
[self.viewController doCustomMethod];
return [super popViewControllerAnimated:animated];
}
@end
Auf der anderen Seite müssen Sie diesen viewController mit Ihrem benutzerdefinierten NavigationController verknüpfen. Führen Sie in der viewDidLoad-Methode für Ihren regulären viewController Folgendes aus:
@implementation MyViewController {
- (void)viewDidLoad
{
[super viewDidLoad];
((CCNavigationController*)self.navigationController).viewController = self;
}
}
Vielleicht ist es ein bisschen zu spät, aber ich wollte das gleiche Verhalten auch schon einmal. Und die Lösung, mit der ich gearbeitet habe, funktioniert ganz gut in einer der Apps, die derzeit im App Store erhältlich sind. Da ich noch niemanden mit einer ähnlichen Methode gesehen habe, möchte ich sie hier teilen. Der Nachteil dieser Lösung ist, dass eine Unterklasse von UINavigationController
erforderlich ist. Obwohl ich Method Swizzling verwende, um das zu vermeiden, bin ich nicht so weit gegangen.
Die Standard-Zurück-Schaltfläche wird also von UINavigationBar
verwaltet. Wenn ein Benutzer auf die Schaltfläche Zurück tippt, fragt UINavigationBar
seinen Stellvertreter, ob er die oberste UINavigationItem
anzeigen soll, indem er navigationBar(_:shouldPop:)
aufruft. UINavigationController
implementiert dies tatsächlich, deklariert jedoch nicht öffentlich, dass UINavigationBarDelegate
übernommen wird (warum !?). Um dieses Ereignis abzufangen, erstellen Sie eine Unterklasse von UINavigationController
, deklarieren die Konformität mit UINavigationBarDelegate
und implementieren navigationBar(_:shouldPop:)
. Geben Sie true
zurück, wenn das oberste Element angezeigt werden soll. Geben Sie false
zurück, wenn es bleiben soll.
Es gibt zwei Probleme. Das erste ist, dass Sie irgendwann die UINavigationController
-Version von navigationBar(_:shouldPop:)
aufrufen müssen. UINavigationBarController
deklariert die Konformität mit UINavigationBarDelegate
jedoch nicht öffentlich, und der Versuch, sie aufzurufen, führt zu einem Fehler bei der Kompilierung. Als Lösung habe ich Objective-C-Laufzeit verwendet, um die Implementierung direkt abzurufen und aufzurufen. Bitte lassen Sie mich wissen, wenn jemand eine bessere Lösung hat.
Das andere Problem ist, dass navigationBar(_:shouldPop:)
zuerst aufgerufen wird, gefolgt von popViewController(animated:)
, wenn der Benutzer auf die Zurück-Schaltfläche tippt. Die Reihenfolge wird umgekehrt, wenn der View-Controller durch Aufrufen von popViewController(animated:)
aufgerufen wird. In diesem Fall verwende ich einen Booleschen Wert, um zu erkennen, ob popViewController(animated:)
vor navigationBar(_:shouldPop:)
aufgerufen wird, was bedeutet, dass der Benutzer auf die Schaltfläche "Zurück" geklickt hat.
Außerdem erstelle ich eine Erweiterung von UIViewController
, damit der Navigationscontroller den Ansichtscontroller fragt, ob er angezeigt werden soll, wenn der Benutzer auf die Schaltfläche "Zurück" tippt. View-Controller können false
zurückgeben und alle erforderlichen Aktionen ausführen und popViewController(animated:)
später aufrufen.
class InterceptableNavigationController: UINavigationController, UINavigationBarDelegate {
// If a view controller is popped by tapping on the back button, `navigationBar(_:, shouldPop:)` is called first follows by `popViewController(animated:)`.
// If it is popped by calling to `popViewController(animated:)`, the order reverses and we need this flag to check that.
private var didCallPopViewController = false
override func popViewController(animated: Bool) -> UIViewController? {
didCallPopViewController = true
return super.popViewController(animated: animated)
}
func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
// If this is a subsequence call after `popViewController(animated:)`, we should just pop the view controller right away.
if didCallPopViewController {
return originalImplementationOfNavigationBar(navigationBar, shouldPop: item)
}
// The following code is called only when the user taps on the back button.
guard let vc = topViewController, item == vc.navigationItem else {
return false
}
if vc.shouldBePopped(self) {
return originalImplementationOfNavigationBar(navigationBar, shouldPop: item)
} else {
return false
}
}
func navigationBar(_ navigationBar: UINavigationBar, didPop item: UINavigationItem) {
didCallPopViewController = false
}
/// Since `UINavigationController` doesn't publicly declare its conformance to `UINavigationBarDelegate`,
/// trying to called `navigationBar(_:shouldPop:)` will result in a compile error.
/// So, we'll have to use Objective-C runtime to directly get super's implementation of `navigationBar(_:shouldPop:)` and call it.
private func originalImplementationOfNavigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
let sel = #selector(UINavigationBarDelegate.navigationBar(_:shouldPop:))
let imp = class_getMethodImplementation(class_getSuperclass(InterceptableNavigationController.self), sel)
typealias ShouldPopFunction = @convention(c) (AnyObject, Selector, UINavigationBar, UINavigationItem) -> Bool
let shouldPop = unsafeBitCast(imp, to: ShouldPopFunction.self)
return shouldPop(self, sel, navigationBar, item)
}
}
extension UIViewController {
@objc func shouldBePopped(_ navigationController: UINavigationController) -> Bool {
return true
}
}
Implementieren Sie in Ihren View-Controllern shouldBePopped(_:)
. Wenn Sie diese Methode nicht implementieren, wird standardmäßig der Ansichts-Controller angezeigt, sobald der Benutzer wie gewohnt auf die Schaltfläche "Zurück" tippt.
class MyViewController: UIViewController {
override func shouldBePopped(_ navigationController: UINavigationController) -> Bool {
let alert = UIAlertController(title: "Do you want to go back?",
message: "Do you really want to go back? Tap on \"Yes\" to go back. Tap on \"No\" to stay on this screen.",
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: nil))
alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { _ in
navigationController.popViewController(animated: true)
}))
present(alert, animated: true, completion: nil)
return false
}
}
Sie können meine Demo anschauen hier .
Dies funktioniert bei mir in Swift:
override func viewWillDisappear(_ animated: Bool) {
if self.navigationController?.viewControllers.index(of: self) == nil {
// back button pressed or back gesture performed
}
super.viewWillDisappear(animated)
}
Wenn Sie ein Storyboard verwenden und von einem Push-Menü kommen, können Sie auch shouldPerformSegueWithIdentifier:sender:
überschreiben.