Ustawienie akcji dla przycisku Wstecz w kontrolerze nawigacyjnym

180

Próbuję zastąpić domyślną akcję przycisku Wstecz w kontrolerze nawigacyjnym. Podałem celowi akcję na przycisku niestandardowym. Dziwne jest to, że przypisując go za pomocą atrybutu backbutton, nie zwraca na nie uwagi, po prostu wyskakuje bieżący widok i wraca do katalogu głównego:

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

Tak szybko, jak ustawić go poprzez leftBarButtonItemna navigationItemnim moje działanie wywołuje jednak następnie przycisk wygląda jak zwykły rundzie zamiast z powrotem jedną strzałką:

self.navigationItem.leftBarButtonItem = backButton;

Jak mogę wywołać moją niestandardową akcję przed powrotem do widoku głównego? Czy istnieje sposób na zastąpienie domyślnej akcji cofania, czy też istnieje metoda, która jest zawsze wywoływana przy opuszczaniu widoku ( viewDidUnloadnie robi tego)?

Papugi
źródło
action: @selector (home)]; potrzebuje: po akcji selektora: @selector (home :)]; w przeciwnym razie nie zadziała
PartySoft,
7
@PartySoft To nie jest prawda, chyba że metoda zostanie zadeklarowana za pomocą dwukropka. Jest całkowicie poprawne, że przyciski do wybierania połączeń nie przyjmują żadnych parametrów.
mbm29414,
3
Dlaczego Apple nie ma przycisku w stylu przypominającym przycisk Wstecz? Wydaje się to dość oczywiste.
JohnK,
Spójrz na rozwiązanie w tym wątku
Jiri Volejnik

Odpowiedzi:

363

Spróbuj umieścić to w kontrolerze widoku, w którym chcesz wykryć prasę:

-(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
źródło
1
jest to zręczne, czyste, ładne i bardzo dobrze przemyślane obejście
boliva,
6
+1 świetny hack, ale nie oferuje kontroli nad animacją pop
mat
3
Nie działa dla mnie, jeśli wyślę wiadomość do uczestnika za pomocą przycisku, a uczestnik wyskoczy z kontrolera - to nadal działa.
SAHM,
21
Innym problemem jest to, że nie można rozróżnić, czy użytkownik nacisnął przycisk Wstecz lub czy programowo wywołałeś [self.navigationController popViewControllerAnimated: YES]
Chase Roberts
10
Tylko wersja FYI: Swift:if (find(self.navigationController!.viewControllers as! [UIViewController],self)==nil)
HEADcRASH
177

I zostały wdrożone UIViewController-BackButtonHandler rozszerzenie. Nie trzeba go podklasować, wystarczy umieścić go w projekcie i zastąpić navigationShouldPopOnBackButtonmetodę w UIViewControllerklasie:

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

Pobierz przykładową aplikację .

onegray
źródło
16
Jest to najczystsze rozwiązanie, jakie widziałem, lepsze i prostsze niż używanie własnego niestandardowego przycisku UIB. Dzięki!
ramirogm
4
To navigationBar:shouldPopItem:nie jest prywatna metoda, ponieważ jest częścią UINavigationBarDelegateprotokołu.
onegray 10.10.2013
1
ale czy UINavigationController już implementuje delegata (do powrotu YES)? czy będzie w przyszłości? podklasowanie jest prawdopodobnie bezpieczniejszą opcją
Sam
8
Właśnie zaimplementowałem to (całkiem fajne BTW) w iOS 7.1 i zauważyłem, że po zwróceniu NOprzycisk Wstecz pozostaje w stanie wyłączonym (wizualnie, ponieważ nadal odbiera i reaguje na zdarzenia dotykowe). Obejrzałem go, dodając elseoświadczenie do shouldPopkontroli i przechodząc między widokami paska nawigacji i ustawiając alphawartość z powrotem na 1, jeśli to konieczne, w bloku animacji: gist.github.com/idevsoftware/9754057
boliva
2
To jedno z najlepszych rozszerzeń, jakie kiedykolwiek widziałem. Dziękuję bardzo.
Srikanth
42

W przeciwieństwie do tego, co powiedział Amagrammer, jest to możliwe. Musisz podklasować swój navigationController. Wyjaśniłem wszystko tutaj (w tym przykładowy kod).

HansPinckaers
źródło
Dokumentacja Apple ( developer.apple.com/iphone/library/documentation/UIKit/... ) mówi, że „Ta klasa nie jest przeznaczona do podklasowania”. Chociaż nie jestem pewien, co przez to rozumieją - mogą oznaczać „normalnie nie powinieneś tego robić”, lub mogą oznaczać „odrzucimy twoją aplikację, jeśli zadzwonisz z naszym kontrolerem” ...
Kuba Suder
Jest to z pewnością jedyny sposób, aby to zrobić. Chciałbym móc przyznać ci więcej punktów Hans!
Adam Eberbach,
1
Czy możesz rzeczywiście zapobiec opuszczeniu widoku za pomocą tej metody? Co sprawiłoby, że zwróciłaby się metoda popViewControllerAnimated, jeśli chcesz, aby widok nie wychodził?
JosephH
1
Tak możesz. Po prostu nie wywołuj metody superklasy w swojej implementacji, pamiętaj! Nie powinieneś tego robić, użytkownik spodziewa się wrócić do nawigacji. Możesz poprosić o potwierdzenie. Zgodnie z dokumentacją Apple popViewController zwraca: „Kontroler widoku, który został usunięty ze stosu”. Więc kiedy nic nie zostanie wyskakujące, powinieneś zwrócić zero;
HansPinckaers
1
@HansPickaers Myślę, że twoja odpowiedź na temat zapobiegania opuszczaniu widoku może być nieco niepoprawna. Jeśli wyświetlę komunikat „potwierdź” z implementacji podklasy popViewControllerAnimated :, pasek nawigacji nadal animuje w górę o jeden poziom w drzewie, niezależnie od tego, co powrócę. Wydaje się, że dzieje się tak, ponieważ kliknięcie przycisku Wstecz wywołuje funkcję PopNavigationItem na pasku nawigacyjnym. Zwracam zero z mojej metody podklas zgodnie z zaleceniami.
deepwinter
15

Wersja szybka:

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

W kontrolerze widoku wystarczy dostosować protokół i wykonać dowolne czynności:

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

Następnie utwórz klasę, powiedzmy NavigationController+BackButton, i po prostu skopiuj i wklej poniższy kod:

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
źródło
Może coś przeoczyłem, ale to nie działa, metoda performSomeActionOnThePressOfABackButton rozszerzenia nigdy nie jest nazywana
Turvy
@FlorentBreton może nieporozumienie? shouldPopOnBackButtonPresspowinien zostać wywołany, dopóki nie będzie żadnych błędów. performSomeActionOnThePressOfABackButtonjest tylko wymyśloną metodą, która nie istnieje.
kgaidis
Zrozumiałem to, dlatego stworzyłem performSomeActionOnThePressOfABackButtonw kontrolerze metodę wykonywania określonej akcji po naciśnięciu przycisku Wstecz, ale ta metoda nigdy nie została wywołana, akcja jest normalnym powrotem wstecz
Turvy
1
Dla mnie też nie działa. Metoda shouldPop nigdy nie jest wywoływana. Czy wyznaczyłeś gdzieś delegata?
Tim Autin
@ TimAutin Właśnie przetestowałem to ponownie i wydaje się, że coś się zmieniło. Kluczową częścią do zrozumienia, że ​​w UINavigationController, navigationBar.delegatejest ustawiony na kontroler nawigacyjny. Tak więc metody POWINNY zostać wywołane. Jednak w Swift nie mogę ich wezwać, nawet w podklasie. Udało mi się jednak wywołać je w Objective-C, więc na razie użyję wersji Objective-C. Może to być szybki błąd.
kgaidis
5

Nie można tego zrobić bezpośrednio. Istnieje kilka alternatyw:

  1. Utwórz własny niestandardowy, UIBarButtonItemktóry będzie sprawdzany po naciśnięciu i wyskakuje, jeśli test się powiedzie
  2. Sprawdź poprawność zawartości pola formularza za pomocą UITextFieldmetody delegowania, takiej jak -textFieldShouldReturn:, która jest wywoływana po naciśnięciu przycisku Returnlub Donena klawiaturze

Minusem pierwszej opcji jest to, że nie można uzyskać dostępu do stylu strzałki w lewo przycisku Wstecz za pomocą niestandardowego przycisku paska. Musisz więc użyć obrazu lub użyć zwykłego przycisku stylu.

Druga opcja jest przydatna, ponieważ pole tekstowe dostajesz z powrotem w metodzie delegowania, dzięki czemu możesz kierować logikę sprawdzania poprawności do określonego pola tekstowego wysłanego do metody oddzwaniania delegata.

Alex Reynolds
źródło
5

Z kilku powodów wątków rozwiązanie wspomniane przez @HansPinckaers nie było dla mnie odpowiednie, ale znalazłem łatwiejszy sposób, aby dotknąć przycisku Wstecz, i chcę to tutaj przypiąć, aby uniknąć godzin oszustwa ktoś inny. Sztuczka jest naprawdę łatwa: wystarczy dodać przezroczysty UIButton jako widok podrzędny do paska UINavigation i ustawić dla niego selektory tak, jakby to był prawdziwy przycisk! Oto przykład użycia Monotouch i C #, ale tłumaczenie na cel-c nie powinno być trudne do znalezienia.

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

Ciekawostka: dla celów testowych i znalezienia dobrych wymiarów mojego fałszywego przycisku ustawiłem kolor tła na niebieski ... I pokazuje się za przyciskiem Wstecz! W każdym razie nadal łapie dotyk dotykający oryginalnego przycisku.

psycho
źródło
3

Ta technika umożliwia zmianę tekstu przycisku „wstecz” bez wpływu na tytuł dowolnego kontrolera widoku lub zmianę tekstu przycisku wstecz podczas animacji.

Dodaj to do metody init w wywołującym kontrolerze widoku:

UIBarButtonItem *temporaryBarButtonItem = [[UIBarButtonItem alloc] init];   
temporaryBarButtonItem.title = @"Back";
self.navigationItem.backBarButtonItem = temporaryBarButtonItem;
[temporaryBarButtonItem release];
Jason Moore
źródło
3

Najprostszy sposób

Możesz użyć metod delegowania UINavigationController. Metoda willShowViewControllerjest wywoływana po naciśnięciu przycisku Wstecz VC. Wykonaj dowolną czynność po naciśnięciu Wstecz btn

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
Zar E Ahmer
źródło
Upewnij się, że kontroler widoku ustawia się jako delegat odziedziczonego elementu navigationController i jest zgodny z protokołem UINavigationControllerDelegate
Justin Milo
3

Oto moje rozwiązanie Swift. W podklasie klasy UIViewController zastąp metodę nawigacji 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
źródło
Metoda przesłonięcia nawigacjiShouldPopOnBackButton w UIViewController nie działa - program wykonuje metodę nadrzędną, a nie metodę przesłoniętą. Jakieś rozwiązanie tego? Czy ktoś ma ten sam problem?
Paweł Cala
wszystko wraca do rootview, jeśli wartość true
Pawriwes
@Pawriwes Oto spisane przeze mnie rozwiązanie, które wydaje się działać dla mnie: stackoverflow.com/a/34343418/826435
kgaidis
3

Znaleziono rozwiązanie, które zachowuje również styl przycisku wstecz. Dodaj następującą metodę do kontrolera widoku.

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


}

Teraz zapewnij funkcjonalność zgodnie z potrzebami w następującej metodzie:

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

Wystarczy zakryć tylny przycisk przezroczystym przyciskiem;)

Sarasranglt
źródło
3

Przesłanianie navigationBar (_ navigationBar: shouldPop) : To nie jest dobry pomysł, nawet jeśli działa. dla mnie generowało losowe awarie podczas nawigowania wstecz. Radzę po prostu zastąpić przycisk Wstecz, usuwając domyślny przycisk Wstecz z elementu Nawigacja i tworząc niestandardowy przycisk Wstecz, jak poniżej:

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

   ...
 
}

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

Opierając się na poprzednich odpowiedziach z UIAlert w Swift5 w sposób asynchroniczny


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

Na twoim kontrolerze


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
źródło
Czy możesz podać szczegółowe informacje na temat tego, co się dzieje z opcją if viewControllers.count <navigationBar.items! .Count {return true}, proszę sprawdzić?
H4Hugo,
// Zapobiega problemowi synchronizacji polegającemu na wyskakiwaniu zbyt wielu elementów nawigacyjnych // i niewystarczającej liczbie kontrolerów widoku lub odwrotnie przed nietypowym stukaniem
brahimm 18.10.19
2

Nie sądzę, że jest to możliwe, łatwo. Uważam, że jedynym sposobem na obejście tego jest zrobienie własnego obrazu strzałki wstecz. Na początku było to dla mnie frustrujące, ale rozumiem, dlaczego, dla zachowania spójności, zostało to pominięte.

Możesz się zbliżyć (bez strzałki), tworząc zwykły przycisk i ukrywając domyślny przycisk powrotu:

self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:@"Servers" style:UIBarButtonItemStyleDone target:nil action:nil] autorelease];
self.navigationItem.hidesBackButton = YES;
Meltemi
źródło
2
Tak, problem polega na tym, że chcę, aby wyglądał jak normalny przycisk Wstecz, po prostu muszę najpierw wywołać moją niestandardową akcję ...
Papugi
2

Jest łatwiejszy sposób tylko przez instacji metodę delegata z poniższych UINavigationBari zastąpić ten ShouldPopItemsposób .

jazzyjef2002
źródło
Myślę, że masz na myśli podklasę klasy UINavigationController i zaimplementować metodę shouldPopItem. To działa dobrze dla mnie. Jednak ta metoda nie powinna po prostu zwracać TAK lub NIE, jak można się spodziewać. Wyjaśnienie i rozwiązanie jest dostępne tutaj: stackoverflow.com/a/7453933/462162
arlomedia
2

Rozwiązanie onegray nie jest bezpieczne. Według oficjalnych dokumentów Apple, https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html powinniśmy tego unikać.

„Jeśli nazwa metody zadeklarowanej w kategorii jest taka sama jak metoda w oryginalnej klasie lub metoda w innej kategorii w tej samej klasie (lub nawet nadklasie), zachowanie nie jest określone, która implementacja metody jest używana w czasie wykonywania. Mniej prawdopodobne jest, że będzie to problem, jeśli używasz kategorii z własnymi klasami, ale może powodować problemy podczas korzystania z kategorii w celu dodania metod do standardowych klas Cocoa lub Cocoa Touch. ”

użytkownik2612791
źródło
2

Za pomocą Swift:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    if self.navigationController?.topViewController != self {
        print("back button tapped")
    }
}
Murray Sagal
źródło
Począwszy od iOS 10 i być może wcześniej nie działa.
Murray Sagal
2

Oto wersja Swift 3 odpowiedzi @oneway na przechwytywanie zdarzenia przycisku wstecz paska nawigacji, zanim zostanie zwolniony. Ponieważ UINavigationBarDelegatenie można go użyć UIViewController, musisz utworzyć delegata, który będzie uruchamiany po navigationBar shouldPopwywołaniu.

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

Następnie w kontrolerze widoku dodaj funkcję delegowania:

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

Zdałem sobie sprawę, że często chcemy dodać kontroler alertów, aby użytkownicy mogli zdecydować, czy chcą wrócić. Jeśli tak, to można zawsze return falsew navigationShouldPopOnBackButton()funkcji kontrolera i zamknąć widok robiąc coś takiego:

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
źródło
Otrzymuję błąd: Value of type 'UIViewController' has no member 'navigationShouldPopOnBackButton' gdy próbuję skompilować kod, na linii if vc.responds(to: #selector(v...Ponadto self.topViewControllerzwraca opcjonalny i nie jest ostrzeżeniem dla że również.
Sankar
FWIW, naprawiłem ten kod, tworząc: let vc = self.topViewController as! MyViewControlleri wydaje się, że do tej pory działał dobrze. Jeśli uważasz, że to właściwa zmiana, możesz edytować kod. Ponadto, jeśli uważasz, że nie należy tego robić, chętnie dowiem się, dlaczego. Dzięki za ten kod. Prawdopodobnie powinieneś napisać o tym post na blogu, ponieważ ta odpowiedź jest zakopana według głosów.
Sankar
@SankarP Przyczyną tego błędu jest MyViewControllerbrak zgodności BackButtonDelegate. Zamiast wymuszać rozpakowywanie, powinieneś zrobić, guard let vc = self.topViewController as? MyViewController else { return true }aby uniknąć możliwej awarii.
Lawliet
Dzięki. Wydaje mi się, że strażnikiem powinno stać się: guard let vc = self.topViewController as? MyViewController else { self.popViewController(animated: true) return true }aby upewnić się, że ekran przesuwa się na właściwą stronę na wypadek, gdyby nie można go poprawnie rzucić. Rozumiem teraz, że navigationBarfunkcja jest wywoływana we wszystkich VC, a nie tylko w kontroler widoków, w którym ten kod istnieje. Czy może warto też zaktualizować kod w odpowiedzi? Dzięki.
Sankar
2

Wersja Swift 4 iOS 11.3:

Opiera się to na odpowiedzi od kgaidis z https://stackoverflow.com/a/34343418/4316579

Nie jestem pewien, kiedy rozszerzenie przestało działać, ale w momencie pisania tego tekstu (Swift 4) wydaje się, że rozszerzenie nie będzie już wykonywane, dopóki nie zadeklarujesz zgodności UINavigationBarDelegate, jak opisano poniżej.

Mam nadzieję, że to pomaga ludziom, którzy zastanawiają się, dlaczego ich rozszerzenie już nie działa.

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

    }
}
Edward L.
źródło
1

Używając zmiennych target i action, w których obecnie pozostawiasz „zero”, powinieneś być w stanie połączyć swoje okna dialogowe zapisywania, aby były wywoływane, gdy przycisk jest „wybrany”. Uważaj, może się to uruchomić w dziwnych momentach.

Zgadzam się głównie z Amagrammerem, ale nie sądzę, że byłoby tak trudno stworzyć przycisk z niestandardową strzałką. Chciałbym tylko zmienić nazwę przycisku Wstecz, zrobić zrzut ekranu, Photoshop potrzebnego rozmiaru przycisku i niech to będzie obraz na górze twojego przycisku.

Tahoe Wilverine
źródło
Zgadzam się, że mógłbyś Photoshopa i myślę, że mógłbym to zrobić, gdybym naprawdę tego chciał, ale teraz postanowiłem zmienić wygląd i poczuć się trochę, aby to działało tak, jak chcę.
John Ballinger
Tak, z wyjątkiem tego, że akcje nie są wyzwalane, gdy są dołączone do backBarButtonItem. Nie wiem, czy to błąd, czy funkcja; możliwe, że nawet Apple nie wie. Jeśli chodzi o ćwiczenie photoshoppingu, znowu będę ostrożny, że Apple odrzuci aplikację za niewłaściwe użycie kanonicznego symbolu.
Amagrammer,
Heads-up: ta odpowiedź została scalona z duplikatu.
Shog9
1

Możesz spróbować uzyskać dostęp do elementu prawego przycisku nawigacji i ustawić jego właściwość selektora ... oto referencja UIBarButtonItem referencja , kolejna rzecz, jeśli ta najdrobniejsza praca, która będzie działać, to ustawić element prawego przycisku paska nawigacyjnego na niestandardowy UIBarButtonItem, który utwórz i ustaw selektor ... mam nadzieję, że to pomoże

Daniel
źródło
Heads-up: ta odpowiedź została scalona z duplikatu.
Shog9
1

W przypadku formularza, który wymaga takiego wkładu użytkownika, zaleciłbym wywołanie go jako „modalnego” zamiast części stosu nawigacji. W ten sposób muszą zająć się biznesem na formularzu, a następnie można go zweryfikować i odrzucić za pomocą niestandardowego przycisku. Możesz nawet zaprojektować pasek nawigacyjny, który będzie wyglądał tak samo jak reszta aplikacji, ale da ci większą kontrolę.

Travis M.
źródło
Heads-up: ta odpowiedź została scalona z duplikatu.
Shog9
1

Aby przechwycić przycisk Wstecz, po prostu przykryj go przezroczystym interfejsem UIControl i przechwytuj dotyk.

@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
źródło
Heads-up: ta odpowiedź została scalona z duplikatu.
Shog9
1

Przynajmniej w Xcode 5 istnieje proste i całkiem dobre (nie idealne) rozwiązanie. W IB przeciągnij element przycisku paska z panelu Narzędzia i upuść go po lewej stronie paska nawigacji, gdzie będzie przycisk Wstecz. Ustaw etykietę na „Wstecz”. Będziesz miał działający przycisk, który możesz powiązać z IBAction i zamknąć viewController. Robię trochę pracy, a następnie uruchamiam segregację odprężającą i działa idealnie.

To, co nie jest idealne, to to, że ten przycisk nie otrzymuje strzałki <i nie przenosi poprzedniego tytułu VC, ale myślę, że można to zarządzać. Dla moich celów ustawiłem nowy przycisk Wstecz jako przycisk „Gotowe”, więc jego cel jest jasny.

W nawigatorze IB możesz także skończyć z dwoma przyciskami Wstecz, ale łatwo jest oznaczyć go dla zachowania przejrzystości.

wprowadź opis zdjęcia tutaj

Dan Loughney
źródło
1

Szybki

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
źródło
1

To podejście zadziałało dla mnie (ale przycisk „Wstecz” nie będzie miał znaku „<”):

- (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
źródło
1

Szybka wersja odpowiedzi @ onegray

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

Teraz w dowolnym kontrolerze, po prostu dostosuj się, RequestsNavigationPopVerificationa to zachowanie zostanie przyjęte domyślnie.

Adam Waite
źródło
1

Posługiwać się isMovingFromParentViewController

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

    if self.isMovingFromParentViewController {
        // current viewController is removed from parent
        // do some work
    }
}
herrk
źródło
Czy możesz wyjaśnić, w jaki sposób dowodzi to, że przycisk Wstecz został naciśnięty?
Murray Sagal
Jest to proste, ale działa tylko wtedy, gdy na pewno wrócisz do tego widoku z dowolnego widoku potomnego, który możesz załadować. Jeśli dziecko pominie ten widok i wróci do rodzica, twój kod nie zostanie wywołany (widok już zniknął bez przeniesienia od rodzica). Ale to ten sam problem z obsługą zdarzeń po naciśnięciu przycisku Wstecz, o co poprosił PO. To prosta odpowiedź na jego pytanie.
CMont
To jest bardzo proste i eleganckie. Kocham to. Tylko jeden problem: uruchomi się również, jeśli użytkownik przesunie palcem, aby wrócić, nawet jeśli anuluje się w połowie. Być może lepszym rozwiązaniem byłoby umieszczenie tego kodu viewDidDisappear. W ten sposób uruchomi się tylko po definitywnym zniknięciu widoku.
Phontaine Judd
1

Odpowiedź od @William jest jednak poprawna, jeśli użytkownik rozpocznie gest przesunięcia do tyłu, viewWillDisappearmetoda zostanie wywołana i nawet selfnie będzie w stosie nawigacyjnym (to znaczy self.navigationController.viewControllersnie będzie zawierać self), nawet jeśli przesunięcie nie jest ukończony, a kontroler widoku nie jest faktycznie wyskakujący. Zatem rozwiązaniem byłoby:

  1. Wyłącz gest przesuwania palcem do tyłu viewDidAppeari zezwalaj tylko na użycie przycisku Wstecz, używając:

    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
    }
  2. Lub po prostu użyj viewDidDisappearzamiast tego w następujący sposób:

    - (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
źródło
0

Rozwiązanie, które znalazłem do tej pory, nie jest zbyt ładne, ale działa dla mnie. Biorąc tę odpowiedź , sprawdzam również, czy popping programowo, czy nie:

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

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

Musisz dodać tę właściwość do kontrolera i ustawić ją na TAK, zanim pojawi się programowo:

self.isPoppingProgrammatically = YES;
[self.navigationController popViewControllerAnimated:YES];
Ferran Maylinch
źródło
0

Znalazłem nowy sposób na zrobienie tego:

Cel C

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

Szybki

override func didMoveToParentViewController(parent: UIViewController?) {
    if parent == nil {
        println("Back Pressed")
    }
}
Ashish Kakkad
źródło