Impostazione dell'azione per il pulsante Indietro nel controller di navigazione


180

Sto cercando di sovrascrivere l'azione predefinita del pulsante Indietro in un controller di navigazione. Ho fornito a un target un'azione sul pulsante personalizzato. La cosa strana è quando lo si assegna sebbene l'attributo backbutton non presti attenzione a loro e salti semplicemente la vista corrente e torni alla radice:

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

Non appena l'ho impostato leftBarButtonItemsu on navigationItem, chiama la mia azione, tuttavia il pulsante sembra un round semplice invece di quello con la freccia indietro:

self.navigationItem.leftBarButtonItem = backButton;

Come faccio a richiamare la mia azione personalizzata prima di tornare alla vista principale? C'è un modo per sovrascrivere l'azione di ritorno predefinita o c'è un metodo che viene sempre chiamato quando si esce da una vista ( viewDidUnloadnon lo fa)?


Azione: @selector (casa)]; ha bisogno di un: dopo l'azione del selettore: @selector (home :)]; altrimenti non funzionerà
PartySoft il

7
@PartySoft Questo non è vero a meno che il metodo non sia dichiarato con i due punti. È perfettamente valido che i pulsanti chiamino i selettori che non accettano parametri.
mbm29414


3
Perché Apple non dovrebbe fornire un pulsante con uno stile a forma di pulsante Indietro? Sembra abbastanza ovvio.
JohnK,

Risposte:


363

Prova a inserirlo nel controller di visualizzazione in cui desideri rilevare la stampante:

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

1
questa è una soluzione semplice, pulita, bella e molto ben pensata
boliva

6
+1 ottimo hack, ma non offre il controllo sull'animazione del pop
matm

3
Non funziona per me se invio un messaggio al delegato tramite un pulsante e il delegato fa apparire il controller - questo si attiva ancora.
SAHM,

21
Un altro problema è che non puoi differenziare se l'utente ha premuto il pulsante Indietro o se hai chiamato in modo programmatico [self.navigationController popViewControllerAnimated: YES]
Chase Roberts,

10
Solo un FYI: versione Swift:if (find(self.navigationController!.viewControllers as! [UIViewController],self)==nil)
hEADcRASH

177

Ho implementato l' estensione UIViewController-BackButtonHandler . Non è necessario sottoclassare nulla, basta inserirlo nel progetto e sovrascrivere il navigationShouldPopOnBackButtonmetodo in UIViewControllerclasse:

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

Scarica l'app di esempio .


16
Questa è la soluzione più pulita che abbia mai visto, migliore e più semplice dell'utilizzo del tuo UIButton personalizzato. Grazie!
Ramirogm,

4
Non navigationBar:shouldPopItem:è un metodo privato poiché fa parte del UINavigationBarDelegateprotocollo.
onegray,

1
ma UINavigationController implementa già il delegato (per tornare YES)? o lo farà in futuro? la sottoclasse è probabilmente un'opzione più sicura
Sam,

8
Ho appena implementato questo (abbastanza interessante BTW) in iOS 7.1 e ho notato che dopo aver restituito NOil pulsante Indietro rimane in uno stato disabilitato (visivamente, perché riceve ancora e reagisce al tocco degli eventi). Ho aggirato il problema aggiungendo una elsedichiarazione al shouldPopcontrollo e scorrendo ciclicamente le visualizzazioni secondarie della barra di navigazione e impostando il alphavalore su 1 se necessario all'interno di un blocco animazione: gist.github.com/idevsoftware/9754057
boliva

2
Questa è una delle migliori estensioni che io abbia mai visto. Grazie mille.
Srikanth,

42

A differenza di Amagrammer, è possibile. Devi sottoclassare il tuo navigationController. Ho spiegato tutto qui (incluso il codice di esempio).


La documentazione di Apple ( developer.apple.com/iphone/library/documentation/UIKit/… ) afferma che "Questa classe non è destinata alla sottoclasse". Anche se non sono sicuro di cosa intendano con questo - potrebbero significare "normalmente non dovresti farlo", oppure potrebbero significare "rifiuteremo la tua app se sbagli con il nostro controller" ...
Kuba Suder

Questo è certamente l'unico modo per farlo. Vorrei poterti assegnare più punti Hans!
Adam Eberbach,

1
Puoi effettivamente impedire a una vista di uscire usando questo metodo? Cosa restituiresti il ​​metodo popViewControllerAnimated se desideri che la vista non venga chiusa?
JosephH,

1
Sì, puoi. Basta non chiamare il metodo superclasse nella tua implementazione, fai attenzione! Non dovresti farlo, l'utente si aspetta di tornare indietro nella navigazione. Quello che puoi fare è chiedere una conferma. Secondo la documentazione di Apple, popViewController restituisce: "Il controller di visualizzazione che è stato estratto dallo stack." Quindi, quando non viene visualizzato nulla, si dovrebbe restituire zero;
HansPinckaers,

1
@HansPickaers Penso che la tua risposta sulla prevenzione della chiusura di una vista possa essere in qualche modo errata. Se visualizzo un messaggio di "conferma" dall'implementazione delle sottoclassi di popViewControllerAnimated :, NavigationBar continua ad animare di un livello nella struttura, indipendentemente da ciò che restituisco. Ciò sembra essere dovuto al fatto che facendo clic sul pulsante Indietro è necessario chiamarePopNavigationItem sulla barra di navigazione. Sto restituendo zero dal mio metodo delle sottoclassi come consigliato.
deepwinter

15

Versione rapida:

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

Nel tuo controller di visualizzazione devi solo conformarti a un protocollo ed eseguire qualsiasi azione ti serva:

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

Quindi crea una classe, diciamo NavigationController+BackButton, e copia e incolla il codice qui sotto:

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

Forse mi sono perso qualcosa, ma non funziona per me, il metodo performSomeActionOnThePressOfABackButton dell'estensione non viene mai chiamato
Turvy,

@FlorentBreton forse un malinteso? shouldPopOnBackButtonPressdovrebbe essere chiamato fintanto che non ci sono bug. performSomeActionOnThePressOfABackButtonè solo un metodo inventato che non esiste.
kgaidis,

L'ho capito, ecco perché ho creato un metodo performSomeActionOnThePressOfABackButtonnel mio controller per eseguire un'azione specifica quando si preme il pulsante Indietro, ma questo metodo non è mai stato chiamato, l'azione è un normale ritorno indietro
Turvy

1
Non funziona neanche per me. Il metodo shouldPop non viene mai chiamato. Hai impostato un delegato da qualche parte?
Tim Autin,

@TimAutin Ho appena provato questo e sembra che qualcosa sia cambiato. Parte chiave per capire che in a UINavigationController, il navigationBar.delegateè impostato sul controller di navigazione. Quindi i metodi DOVREBBERO essere chiamati. Tuttavia, in Swift, non riesco a farli chiamare, nemmeno in una sottoclasse. Tuttavia, li ho fatti chiamare in Objective-C, quindi per ora userei solo la versione di Objective-C. Potrebbe essere un bug Swift.
kgaidis,

5

Non è possibile farlo direttamente. Ci sono un paio di alternative:

  1. Crea la tua personalizzazione UIBarButtonItemche convalida al tocco e si apre se il test ha esito positivo
  2. Convalida del contenuto del campo modulo utilizzando a UITextField metodo delegato, ad esempio -textFieldShouldReturn:, che viene chiamato dopo aver premuto il pulsante Returno Donesulla tastiera

Il rovescio della medaglia della prima opzione è che lo stile della freccia rivolta verso sinistra del pulsante Indietro non è accessibile da un pulsante della barra personalizzato. Quindi devi usare un'immagine o andare con un pulsante di stile normale.

La seconda opzione è utile perché il campo di testo viene ripristinato nel metodo delegato, quindi è possibile indirizzare la logica di convalida al campo di testo specifico inviato al metodo di richiamata delegato.


5

Per alcuni motivi di threading, la soluzione menzionata da @HansPinckaers non era adatta a me, ma ho trovato un modo molto più semplice per cogliere un tocco sul pulsante Indietro e voglio bloccarlo qui nel caso in cui ciò potesse evitare ore di inganni per qualcun altro. Il trucco è davvero semplice: basta aggiungere un UIButton trasparente come sottoview alla tua UINavigationBar e impostare i selettori per lui come se fosse il vero pulsante! Ecco un esempio usando Monotouch e C #, ma la traduzione in obiettivo-c non dovrebbe essere troppo difficile da trovare.

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

Curiosità: a scopo di test e per trovare buone dimensioni per il mio pulsante falso, ho impostato il colore di sfondo su blu ... E si mostra dietro il pulsante Indietro! Ad ogni modo, cattura ancora qualsiasi tocco mirando al pulsante originale.


3

Questa tecnica ti consente di cambiare il testo del pulsante "indietro" senza influenzare il titolo di nessuno dei controller di visualizzazione o vedere il testo del pulsante Indietro cambiare durante l'animazione.

Aggiungi questo al metodo init nel controller della vista chiamante :

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

3

Il modo più semplice

È possibile utilizzare i metodi delegati di UINavigationController. Il metodo willShowViewControllerviene chiamato quando viene premuto il pulsante Indietro del VC. Fai quello che vuoi quando btn viene premuto

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

Assicurati che il tuo controller di visualizzazione si configuri come delegato del controller di navigazione ereditato e sia conforme al protocollo UINavigationControllerDelegate
Justin Milo,

3

Ecco la mia soluzione Swift. Nella sottoclasse di UIViewController, sovrascrivere il metodo 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
    }

}

Il metodo di override navigationShouldPopOnBackButton in UIViewController non funziona - Il programma esegue il metodo genitore non quello di sostituzione. Qualche soluzione per quello? Qualcuno ha lo stesso problema?
Pawel Cala,

risale al rootview se torna vero
Pawriwes

@Pawriwes Ecco una soluzione che ho scritto fino che sembra funzionare per me: stackoverflow.com/a/34343418/826435
kgaidis

3

Trovata una soluzione che mantiene anche lo stile del pulsante Indietro. Aggiungi il seguente metodo al tuo controller di visualizzazione.

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


}

Ora fornisce una funzionalità secondo necessità nel seguente metodo:

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

Non fa altro che coprire il pulsante Indietro con un pulsante trasparente;)


3

Sostituzione di navigationBar (_ navigationBar: shouldPop) : questa non è una buona idea, anche se funziona. per me ha generato arresti casuali sulla navigazione indietro. Ti consiglio di sovrascrivere il pulsante Indietro rimuovendo il pulsante Indietro predefinito da navigationItem e creando un pulsante Indietro personalizzato come di seguito:

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

   ...
 
}

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

Basandosi sulle risposte precedenti con UIAlert in Swift5 in modo asincrono


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

Sul tuo 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)
            })
            ])
   
    }

}

Potete fornire dettagli su cosa sta succedendo con il controllo if viewControllers.count <navigationBar.items! .Count {return true} per favore?
H4Hugo,

// Impedisce un problema di sincronizzazione del
pop

2

Non credo sia possibile, facilmente. L'unico modo in cui credo di aggirare questo è di creare la tua immagine della freccia del pulsante indietro per posizionarla lassù. All'inizio è stato frustrante per me, ma vedo perché, per motivi di coerenza, è stato lasciato fuori.

Puoi avvicinarti (senza la freccia) creando un pulsante normale e nascondendo il pulsante Indietro predefinito:

self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:@"Servers" style:UIBarButtonItemStyleDone target:nil action:nil] autorelease];
self.navigationItem.hidesBackButton = YES;

2
Sì, il problema è che voglio che assomigli al normale pulsante Indietro, basta che chiami prima la mia azione personalizzata ...
Parrots

2

C'è un modo più semplice semplicemente sottoclassando il metodo delegato di UINavigationBare sovrascrivendo il ShouldPopItemmetodo .


Penso che intendi dire sottoclassare la classe UINavigationController e implementare un metodo shouldPopItem. Funziona bene per me. Tuttavia, tale metodo non dovrebbe semplicemente restituire SÌ o NO come previsto. Una spiegazione e la soluzione è disponibile qui: stackoverflow.com/a/7453933/462162
arlomedia

2

La soluzione di onegray non è sicura. Secondo i documenti ufficiali di Apple, https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html , dovremmo evitare di farlo.

"Se il nome di un metodo dichiarato in una categoria è lo stesso di un metodo nella classe originale o di un metodo in un'altra categoria della stessa classe (o persino una superclasse), il comportamento non è definito su quale implementazione del metodo viene utilizzata in fase di esecuzione. È meno probabile che si tratti di un problema se si utilizzano categorie con le proprie classi, ma possono causare problemi quando si utilizzano le categorie per aggiungere metodi alle classi standard Cocoa o Cocoa Touch. "


2

Utilizzando Swift:

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

A partire da iOS 10, e forse prima, questo non funziona più.
Murray Sagal,

2

Ecco la versione Swift 3 della risposta di @oneway per catturare l'evento del pulsante indietro della barra di navigazione prima che venga attivato. Poiché UINavigationBarDelegatenon è possibile utilizzarlo UIViewController, è necessario creare un delegato che verrà attivato quando navigationBar shouldPopviene chiamato.

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

E poi, nella vista controller aggiungi la funzione delegato:

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

Mi sono reso conto che spesso vogliamo aggiungere un controller di avviso per gli utenti per decidere se vogliono tornare indietro. Se è così, si può sempre return falsein navigationShouldPopOnBackButton()funzione e chiudere il controller della vista facendo qualcosa di simile a questo:

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

Ricevo un errore: Value of type 'UIViewController' has no member 'navigationShouldPopOnBackButton' quando provo a compilare il codice, per la riga if vc.responds(to: #selector(v...Inoltre, viene self.topViewControllerrestituito un valore facoltativo e viene visualizzato anche un avviso.
Sankar,

FWIW, ho corretto quel codice creando: let vc = self.topViewController as! MyViewControllere sembra funzionare bene finora. Se ritieni che si tratti di una modifica corretta, puoi modificare il codice. Inoltre, se ritieni che non dovrebbe essere fatto, sarò felice di sapere perché. Grazie per questo codice Probabilmente dovresti scrivere un post sul blog su questo, poiché questa risposta è sepolta secondo i voti.
Sankar,

@SankarP Il motivo per cui hai riscontrato l'errore è che MyViewControllerpotresti non essere conforme BackButtonDelegate. Invece di forzare lo scartamento, dovresti fare guard let vc = self.topViewController as? MyViewController else { return true }per evitare possibili crash.
Lawliet,

Grazie. Penso che la frase di guardia dovrebbe diventare: guard let vc = self.topViewController as? MyViewController else { self.popViewController(animated: true) return true }per assicurarsi che lo schermo si stia spostando sulla pagina giusta nel caso in cui non possa essere trasmesso correttamente. Ora capisco che la navigationBarfunzione è chiamata in tutti i VC e non solo nel viewcontroller in cui esiste questo codice. Può essere bello aggiornare anche il codice nella tua risposta? Grazie.
Sankar,

2

Swift 4 iOS 11.3 versione:

Questo si basa sulla risposta di kgaidis da https://stackoverflow.com/a/34343418/4316579

Non sono sicuro quando l'estensione ha smesso di funzionare, ma al momento della stesura di questo documento (Swift 4), sembra che l'estensione non verrà più eseguita a meno che non si dichiari la conformità UINavigationBarDelegate come descritto di seguito.

Spero che questo aiuti le persone che si chiedono perché la loro estensione non funziona più.

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

    }
}

1

Utilizzando le variabili target e action che si stanno lasciando 'zero', si dovrebbe essere in grado di collegare le finestre di dialogo di salvataggio in modo che vengano chiamate quando il pulsante è "selezionato". Attenzione, questo potrebbe attivarsi in strani momenti.

Concordo principalmente con Amagrammer, ma non credo che sarebbe così difficile creare il pulsante con la freccia personalizzata. Vorrei solo rinominare il pulsante Indietro, scattare una schermata, Photoshop le dimensioni del pulsante necessarie e avere quella immagine nella parte superiore del pulsante.


Sono d'accordo che potresti Photoshop e penso che potrei farlo se lo volessi davvero, ma ora ho deciso di cambiare l'aspetto e sentirmi un po 'per farlo funzionare come voglio.
John Ballinger,

Sì, tranne per il fatto che le azioni non vengono attivate quando sono associate a backBarButtonItem. Non so se questo è un bug o una funzionalità; è possibile che anche Apple non lo sappia. Per quanto riguarda l'esercizio di photoshopping, sarei ancora cauto che Apple rifiuterebbe l'app per l'uso improprio di un simbolo canonico.
Amagrammer,

Heads-up: questa risposta è stata unita da un duplicato.
Shog9

1

Puoi provare ad accedere alla voce Tasto destro di NavigationBars e impostarne la proprietà del selettore ... ecco un riferimento UIBarButtonItem di riferimento , un'altra cosa se questo doenst lavoro che funzionerà sicuramente è, imposta l'elemento del pulsante destro della barra di navigazione su un UIBarButtonItem personalizzato crea e imposta il suo selettore ... spero che questo aiuti


Heads-up: questa risposta è stata unita da un duplicato.
Shog9

1

Per un modulo che richiede input dell'utente come questo, consiglierei di invocarlo come "modale" anziché come parte del tuo stack di navigazione. In questo modo devono occuparsi degli affari nel modulo, quindi è possibile convalidarlo e chiuderlo utilizzando un pulsante personalizzato. Puoi persino progettare una barra di navigazione che assomigli al resto della tua app ma ti dia un maggiore controllo.


Heads-up: questa risposta è stata unita da un duplicato.
Shog9

1

Per intercettare il pulsante Indietro, è sufficiente coprirlo con un UIControl trasparente e intercettare i tocchi.

@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

Heads-up: questa risposta è stata unita da un duplicato.
Shog9

1

Almeno in Xcode 5, esiste una soluzione semplice e abbastanza buona (non perfetta). In IB, trascina un elemento pulsante barra dal riquadro Utilità e rilascialo sul lato sinistro della barra di navigazione dove si trova il pulsante Indietro. Imposta l'etichetta su "Indietro". Avrai un pulsante funzionante che puoi collegare alla tua IBAction e chiudere il tuo viewController. Sto facendo un po 'di lavoro e quindi innescando un svolgersi segue e funziona perfettamente.

Ciò che non è l'ideale è che questo pulsante non ottiene la <freccia e non porta avanti il ​​titolo VC precedente, ma penso che questo possa essere gestito. Per i miei scopi, ho impostato il nuovo pulsante Indietro in modo che sia "Fatto" in modo che il suo scopo sia chiaro.

Si finisce anche con due pulsanti Indietro nel navigatore IB, ma è abbastanza facile etichettarlo per chiarezza.

inserisci qui la descrizione dell'immagine


1

veloce

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
}

1

Questo approccio ha funzionato per me (ma il pulsante "Indietro" non avrà il segno "<"):

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

1

Versione rapida della risposta di @ 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
    }
}

Ora in qualsiasi controller, basta conformarsi RequestsNavigationPopVerificatione questo comportamento è adottato per impostazione predefinita.


1

Uso isMovingFromParentViewController

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

    if self.isMovingFromParentViewController {
        // current viewController is removed from parent
        // do some work
    }
}

Potresti spiegare ulteriormente come questo dimostra che il pulsante Indietro è stato toccato?
Murray Sagal,

Questo è semplice, ma funziona solo se si è sicuri di tornare a quella vista da qualsiasi vista figlio che è possibile caricare. Se il bambino salta questa vista mentre risale al genitore, il tuo codice non verrà chiamato (la vista era già scomparsa senza essersi spostata dal genitore). Ma questo è lo stesso problema con la gestione degli eventi solo sul trigger del pulsante Indietro, come richiesto dall'OP. Quindi questa è una semplice risposta alla sua domanda.
CMont

Questo è super semplice ed elegante. Lo adoro. Solo un problema: questo si attiva anche se l'utente scorre per tornare indietro, anche se si annulla a metà. Forse una soluzione migliore sarebbe quella di inserire questo codice viewDidDisappear. In questo modo sparerà solo quando la vista sarà definitivamente scomparsa.
Phontaine Judd il

1

La risposta di @William è corretta, tuttavia, se l'utente avvia un gesto di scorrimento per tornare indietro, il viewWillDisappearmetodo viene chiamato e selfnon si troverà nemmeno nello stack di navigazione (ovvero, self.navigationController.viewControllersnon conterrà self), anche se lo scorrimento non è completato e il controller di visualizzazione non viene effettivamente visualizzato. Pertanto, la soluzione sarebbe:

  1. Disattiva il gesto di scorrimento per tornare indietro viewDidAppeare consenti solo di utilizzare il pulsante Indietro, usando:

    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
    }
  2. O semplicemente usa viewDidDisappearinvece, come segue:

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

0

La soluzione che ho trovato finora non è molto bella, ma funziona per me. Prendendo questa risposta , controllo anche se sto saltando programmaticamente o meno:

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

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

Devi aggiungere quella proprietà al tuo controller e impostarla su SÌ prima di far apparire programmaticamente:

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

0

Trovato un nuovo modo di farlo:

Objective-C

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

veloce

override func didMoveToParentViewController(parent: UIViewController?) {
    if parent == nil {
        println("Back Pressed")
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.