wywołanie zwrotne przycisku wstecz w navigationController w iOS


102

Umieściłem widok na kontrolerze nawigacyjnym i po naciśnięciu przycisku Wstecz automatycznie przechodzi do poprzedniego widoku. Chcę zrobić kilka rzeczy, gdy przycisk Wstecz zostanie naciśnięty przed usunięciem widoku ze stosu. Jaka jest funkcja wywołania zwrotnego przycisku Wstecz?



Sprawdź to [rozwiązanie] [1], które również zachowuje styl przycisku Wstecz. [1]: stackoverflow.com/a/29943156/3839641
Sarasranglt

Odpowiedzi:


162

Williama Jockusch za odpowiedź rozwiązać ten problem z łatwym sztuczki.

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

32
Ten kod jest wykonywany nie tylko wtedy, gdy użytkownik naciśnie przycisk Wstecz, ale w każdym przypadku pojawia się widok (np. Po kliknięciu przycisku „Gotowe” lub „Zapisz” po prawej stronie).
znaczenie-ma znaczenie

7
Lub podczas przechodzenia do nowego widoku.
GuybrushThreepwood

Jest to również wywoływane, gdy użytkownik przesuwa obraz od lewej krawędzi (InteractivePopGestureRecognizer). W moim przypadku szczególnie szukam, gdy użytkownik naciska z powrotem, NIE przesuwając od lewej krawędzi.
Kyle Clegg

2
Nie oznacza to, że przyczyną był przycisk Wstecz. Może to być na przykład odprężająca kolejka.
smileBot

1
Mam wątpliwości, dlaczego nie mielibyśmy tego zrobić z myślą o zniknięciu?
JohnVanDijk

85

Moim zdaniem najlepsze rozwiązanie.

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

Ale działa tylko z iOS5 +


3
Ta technika nie rozróżnia między dotknięciem przycisku Wstecz a odprężeniem.
smileBot

Metoda willMoveToParentViewController i viewWillDisappear nie wyjaśnia, że ​​kontroler musi zostać zniszczony, didMoveToParentViewController ma rację
Hank

27

Prawdopodobnie lepiej jest zastąpić przycisk Backbutton, aby móc obsłużyć zdarzenie przed wyświetleniem widoku na takie rzeczy, jak potwierdzenie użytkownika.

in viewDidLoad utwórz UIBarButtonItem i ustaw self.navigationItem.leftBarButtonItem na przekazanie w sel

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

}

Następnie możesz wykonać takie czynności, jak podniesienie UIAlertView, aby potwierdzić akcję, a następnie wyświetlenie kontrolera widoku itp.

Lub zamiast tworzyć nowy przycisk Backbutton, można dostosować metody delegata UINavigationController do wykonywania akcji po naciśnięciu przycisku Wstecz.


UINavigationControllerDelegateNie ma metod, które są wywoływane gdy back-przycisk jest na podsłuchu.
znaczenie-ma znaczenie

Technika ta umożliwia walidację danych kontrolera widoku i warunkowy zwrot z przycisku wstecz kontrolera nawigacji.
gjpc,

To rozwiązanie łamie funkcję przesuwania krawędzi w systemie iOS 7+
Liron Yahdav

9

To jest właściwy sposób, aby to wykryć.

- (void)willMoveToParentViewController:(UIViewController *)parent{
    if (parent == nil){
        //do stuff

    }
}

ta metoda jest wywoływana również wtedy, gdy przekazywany jest widok. Zatem sprawdzenie parent == nil służy do wyrzucenia kontrolera widoku ze stosu


9

Skończyłem z takimi rozwiązaniami. Gdy stukamy przycisk wstecz, wywoływana jest metoda viewDidDisappear. możemy sprawdzić, wywołując selektor isMovingFromParentViewController, który zwraca wartość true. możemy przekazać dane z powrotem (używając Delegata). Mam nadzieję, że to komuś pomoże.

-(void)viewDidDisappear:(BOOL)animated{

    if (self.isMovingToParentViewController) {

    }
    if (self.isMovingFromParentViewController) {
       //moving back
        //pass to viewCollection delegate and update UI
        [self.delegateObject passBackSavedData:self.dataModel];

    }
}

Nie zapomnij[super viewDidDisappear:animated]
SamB

9

Może jest już trochę za późno, ale chciałem też wcześniej takiego zachowania. Rozwiązanie, które wybrałem, działa całkiem dobrze w jednej z aplikacji obecnie dostępnych w App Store. Ponieważ nie widziałem nikogo, kto stosuje podobną metodę, chciałbym się nią tutaj podzielić. Wadą tego rozwiązania jest to, że wymaga podklasy UINavigationController. Chociaż użycie Method Swizzling może pomóc w uniknięciu tego, nie posunąłem się tak daleko.

Tak więc domyślny przycisk Wstecz jest faktycznie zarządzany przez UINavigationBar. Gdy użytkownik naciśnie przycisk Wstecz, UINavigationBarzapytaj delegata, czy powinien otworzyć górną część UINavigationItem, dzwoniąc navigationBar(_:shouldPop:). UINavigationControllerfaktycznie implementuje to, ale nie deklaruje publicznie, że przyjmuje UINavigationBarDelegate(dlaczego !?). Aby przechwycić to zdarzenie, utwórz podklasę UINavigationController, zadeklaruj jej zgodność UINavigationBarDelegatei zaimplementuj navigationBar(_:shouldPop:). Zwróć, truejeśli górny element powinien zostać usunięty. Wróć, falsejeśli powinno zostać.

Są dwa problemy. Po pierwsze, musisz kiedyś wywołać UINavigationControllerwersję programu navigationBar(_:shouldPop:). Ale UINavigationBarControllernie deklaruje publicznie, że jest zgodny z UINavigationBarDelegate, próba wywołania spowoduje błąd czasu kompilacji. Rozwiązaniem, które wybrałem, jest użycie środowiska uruchomieniowego Objective-C, aby bezpośrednio pobrać implementację i wywołać ją. Daj mi znać, jeśli ktoś ma lepsze rozwiązanie.

Innym problemem jest to, że navigationBar(_:shouldPop:)najpierw wywoływana jest następująca popViewController(animated:)po dotknięciu przycisku Wstecz. Kolejność jest odwracana, jeśli kontroler widoku jest otwierany przez wywołanie popViewController(animated:). W tym przypadku używam wartości logicznej, aby wykryć, czy popViewController(animated:)została wywołana przed, navigationBar(_:shouldPop:)co oznacza, że ​​użytkownik nacisnął przycisk Wstecz.

Robię również rozszerzenie, UIViewControlleraby umożliwić kontrolerowi nawigacji pytanie kontrolera widoku, czy należy go otworzyć, jeśli użytkownik naciśnie przycisk Wstecz. Kontrolery widoku mogą wrócić falsei wykonać niezbędne czynności, a popViewController(animated:)później zadzwonić .

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

A widząc kontrolery, implementuj shouldBePopped(_:). Jeśli nie zaimplementujesz tej metody, domyślnym zachowaniem będzie wyskakiwanie kontrolera widoku, gdy tylko użytkownik naciśnie przycisk Wstecz, tak jak zwykle.

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

Możesz obejrzeć moje demo tutaj .

wprowadź opis obrazu tutaj


To świetne rozwiązanie, które powinno znaleźć się w poście na blogu! Wydaje się, że to przesada jak na to, czego teraz szukam, ale w innych okolicznościach z pewnością warto spróbować.
ASSeeger

6

Komunikat „PRZED usunięciem widoku ze stosu”:

- (void)willMoveToParentViewController:(UIViewController *)parent{
    if (parent == nil){
        NSLog(@"do whatever you want here");
    }
}

5

Jest bardziej odpowiedni sposób niż zapytanie ViewControllers. Możesz ustawić swój kontroler jako delegata paska nawigacji, który ma przycisk Wstecz. Oto przykład. W implementacji kontrolera, w którym chcesz obsłużyć naciśnięcie przycisku wstecz, powiedz mu, że zaimplementuje protokół UINavigationBarDelegate:

@interface MyViewController () <UINavigationBarDelegate>

Następnie gdzieś w kodzie inicjalizacyjnym (prawdopodobnie w viewDidLoad) ustaw kontroler jako delegata paska nawigacji:

self.navigationController.navigationBar.delegate = self;

Na koniec zaimplementuj metodę shouldPopItem. Ta metoda jest wywoływana zaraz po naciśnięciu przycisku Wstecz. Jeśli masz wiele kontrolerów lub elementów nawigacyjnych w stosie, prawdopodobnie będziesz chciał sprawdzić, które z tych elementów nawigacyjnych są wyskakujące (parametr elementu), abyś robił swoje niestandardowe rzeczy tylko wtedy, gdy się tego spodziewasz. Oto przykład:

-(BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
    NSLog(@"Back button got pressed!");
    //if you return NO, the back button press is cancelled
    return YES;
}

4
nie działa dla mnie… szkoda, ponieważ jest chudy. „*** Zamykanie aplikacji z powodu nieprzechwyconego wyjątku„ NSInternalInconsistencyException ”, przyczyna:„ Nie można ręcznie ustawić delegata na
pasku

To niestety nie zadziała z UINavigationController, zamiast tego potrzebujesz standardowego UIViewController z UINavigationBar w nim. Oznacza to, że nie możesz skorzystać z kilku funkcji automatycznego wypychania i wyskakiwania kontrolera widoku, które zapewnia NavigationController. Przepraszam!
Carlos Guzman,

Po prostu użyłem UINavigationBar zamiast NavigationBarController i działa dobrze. Wiem, że pytanie dotyczy NavigationBarController, ale to rozwiązanie jest chude.
aplikacje zjednoczone

3

Jeśli nie możesz użyć metody „viewWillDisappear” lub podobnej, spróbuj utworzyć podklasę UINavigationController. To jest klasa nagłówka:

#import <Foundation/Foundation.h>
@class MyViewController;

@interface CCNavigationController : UINavigationController

@property (nonatomic, strong) MyViewController *viewController;

@end

Klasa realizacji:

#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

Z drugiej strony musisz połączyć ten viewController ze swoim niestandardowym NavigationController, więc w swojej metodzie viewDidLoad dla zwykłego viewController wykonaj następujące czynności:

@implementation MyViewController {
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        ((CCNavigationController*)self.navigationController).viewController = self;
    }
}

3

Oto inny sposób, który zaimplementowałem (nie testowałem go z rozwijaniem płynności, ale prawdopodobnie nie będzie się różnicował, jak stwierdzili inni w odniesieniu do innych rozwiązań na tej stronie), aby nadrzędny kontroler widoku wykonywał akcje przed podrzędnym VC, które wypchnął zostaje wyskakujący ze stosu widoków (użyłem tego kilka poziomów w dół od oryginalnego UINavigationController). Można tego również użyć do wykonywania akcji, zanim childVC zostanie wypchnięte. Ma to dodatkową zaletę w postaci pracy z przyciskiem Wstecz systemu iOS, zamiast konieczności tworzenia niestandardowego UIBarButtonItem lub UIButton.

  1. Poproś swojego rodzica VC o przyjęcie UINavigationControllerDelegateprotokołu i zarejestrowanie się na wiadomości delegatów:

    MyParentViewController : UIViewController <UINavigationControllerDelegate>
    
    -(void)viewDidLoad {
        self.navigationcontroller.delegate = self;
    }
  2. Zaimplementuj tę UINavigationControllerDelegatemetodę instancji w 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;
    }
  3. Jeśli określisz konkretną funkcję zwrotną w powyższej UINavigationControllerDelegatemetodzie wystąpienia

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

    }


1

Oto, co działa u mnie w Swift:

override func viewWillDisappear(_ animated: Bool) {
    if self.navigationController?.viewControllers.index(of: self) == nil {
        // back button pressed or back gesture performed
    }

    super.viewWillDisappear(animated)
}

0

Jeśli używasz Storyboard i pochodzisz z segue push, możesz również po prostu zastąpić shouldPerformSegueWithIdentifier:sender:.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.