Definir ação para o botão Voltar no controlador de navegação

Estou tentando sobrescrever a ação padrão do botão Voltar em um controlador de navegação. Eu forneci um alvo para uma ação no botão personalizado. O mais estranho é quando atribuí-lo através do atributo backbutton, ele não presta atenção a eles e apenas aparece a visão atual e volta para a raiz:

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

Assim que eu configuro através do leftBarButtonItem no navigationItem, ele chama minha ação, entretanto o botão se parece com um redondo simples ao invés do anterior com setas:

 self.navigationItem.leftBarButtonItem = backButton; 

Como posso fazer isso para chamar minha ação personalizada antes de voltar para a visualização raiz? Existe uma maneira de replace a ação de retorno padrão, ou existe um método que é sempre chamado ao sair de uma view (viewDidUnload não faz isso)?

    Tente colocar isso no controlador de visualização onde você deseja detectar a impressora:

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

    Eu implementei a extensão UIViewController-BackButtonHandler . Não é necessário subclassificar nada, basta colocá-lo em seu projeto e replace o método navigationShouldPopOnBackButton na class UIViewController :

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

    Baixe o aplicativo de amostra .

    Ao contrário de Amagrammer, é possível. Você tem que subclassificar seu navigationController. Eu expliquei tudo aqui (incluindo código de exemplo): http://www.hanspinckaers.com/custom-action-on-back-button-uinavigationcontroller

    Versão rápida:

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

    Em seu controlador de visualização, você apenas se ajusta a um protocolo e executa qualquer ação necessária:

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

    Em seguida, crie uma class, digamos NavigationController+BackButton , e apenas copie e cole o código abaixo:

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

    Não é possível fazer diretamente. Existem algumas alternativas:

    1. Crie seu próprio UIBarButtonItem personalizado que valida ao tocar e aparece se o teste passar
    2. Valide o conteúdo do campo de formulário usando um método delegado UITextField , como -textFieldShouldReturn: que é chamado depois que o botão Return ou Done é pressionado no teclado

    A desvantagem da primeira opção é que o estilo de seta para a esquerda do botão Voltar não pode ser acessado a partir de um botão de barra personalizada. Então você tem que usar uma imagem ou ir com um botão de estilo regular.

    A segunda opção é interessante porque você obtém o campo de texto de volta no método delegado, portanto, pode direcionar sua lógica de validação para o campo de texto específico enviado para o método de retorno de chamada do delegado.

    Por algumas razões, a solução mencionada pelo @HansPinckaers não era o certo para mim, mas eu achei uma maneira muito mais fácil de pegar um toque no botão Voltar, e eu quero fixar isso aqui, caso isso possa evitar horas de decepções por alguém. O truque é realmente fácil: basta adicionar um UIButton transparente como uma sub-view ao seu UINavigationBar e configurar seus seletores para ele como se fosse o botão real! Aqui está um exemplo usando Monotouch e C #, mas a tradução para objective-c não deve ser muito difícil de encontrar.

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

    Fato divertido: para fins de teste e para encontrar boas dimensões para o meu botão falso, eu definir sua cor de fundo para azul … E mostra por trás do botão de volta! De qualquer forma, ele ainda captura qualquer toque que tenha como alvo o botão original.

    Essa técnica permite que você altere o texto do botão “voltar” sem afetar o título de qualquer um dos controladores de visualização ou ver o texto do botão Voltar durante a animação.

    Adicione isto ao método init no controlador de exibição de chamadas :

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

    Caminho mais fácil

    Você pode usar os methods de delegado do UINavigationController. O método willShowViewController é chamado quando o botão de volta do seu VC é pressionado. Faça o que você quiser quando voltar btn pressionado

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

    Encontrou uma solução que também mantém o estilo do botão Voltar. Adicione o seguinte método ao seu controlador de exibição.

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

    Agora, forneça uma funcionalidade conforme necessário no seguinte método:

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

    Tudo o que faz é cobrir o botão de volta com um botão transparente;)

    Eu não acredito que isso seja possível, facilmente. A única maneira que eu acredito para contornar isso é fazer sua própria imagem de seta do botão de voltar para colocar lá em cima. Foi frustrante para mim no início, mas vejo por que, por uma questão de consistência, ficou de fora.

    Você pode se aproximar (sem a flecha) criando um botão regular e ocultando o botão de voltar padrão:

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

    Há uma maneira mais fácil apenas subclassificando o método delegado da UINavigationBar e substituindo o método ShouldPopItem .

    Aqui está minha solução Swift. Na sua subclass de UIViewController, sobrescreva o método 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 } } 

    A solução da onegray não é segura. De acordo com os documentos oficiais da Apple, https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html , devemos evitar fazer isso.

    “Se o nome de um método declarado em uma categoria é o mesmo que um método na class original, ou um método em outra categoria na mesma class (ou até mesmo uma superclass), o comportamento é indefinido quanto a qual método a implementação é usada Isso é menos provável de ser um problema se você estiver usando categorias com suas próprias classs, mas pode causar problemas ao usar categorias para adicionar methods às classs padrão Cocoa ou Cocoa Touch. ”

    Usando o Swift:

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

    Aqui está a versão Swift 3 da resposta do @oneway para capturar o evento do botão de barra de navegação antes de ser acionado. Como UINavigationBarDelegate não pode ser usado para o UIViewController , você precisa criar um delegado que será acionado quando o navigationBar shouldPop for chamado.

     @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, em seguida, no controlador de exibição, inclua a function delegada:

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

    Eu percebi que muitas vezes queremos adicionar um controlador de alerta para os usuários decidirem se querem voltar. Em caso afirmativo, você sempre pode return false na function navigationShouldPopOnBackButton() e fechar seu controlador de modo de exibição, fazendo algo assim:

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

    Usando as variables ​​target e action que você está deixando atualmente ‘nil’, você deve poder conectar seus save-dialogs de forma que eles sejam chamados quando o botão estiver “selecionado”. Cuidado, isso pode ser acionado em momentos estranhos.

    Eu concordo principalmente com o Amagrammer, mas não acho que seria tão difícil fazer o botão com a flecha personalizada. Gostaria apenas de renomear o botão Voltar, fazer uma captura de canvas, photoshop o tamanho do botão necessário e ter que ser a imagem na parte superior do seu botão.

    Você pode tentar acessar o item de botão direito do NavigationBars e definir sua propriedade de seletor … heres uma referência UIBarButtonItem de referência , outra coisa se este trabalho doen que def trabalho é, defina o item de botão direito da barra de navegação para um personalizado UIBarButtonItem que você criar e definir seu seletor … espero que isso ajude

    Para um formulário que requer input do usuário como essa, eu recomendaria chamá-lo como “modal” em vez de parte da sua pilha de navegação. Dessa forma, eles precisam cuidar dos negócios no formulário, depois você pode validá-lo e descartá-lo usando um botão personalizado. Você pode até projetar uma barra de navegação com a mesma aparência do resto do seu aplicativo, mas com mais controle.

    Pelo menos no Xcode 5, existe uma solução simples e muito boa (não perfeita). No IB, arraste um item de botão de barra do painel Utilitários e solte-o no lado esquerdo da barra de navegação, onde o botão Voltar estaria. Defina o label como “Voltar”. Você terá um botão funcional que pode ser vinculado ao seu IBAction e fechar seu viewController. Eu estou fazendo algum trabalho e, em seguida, desencadeando um desenrolar segue e funciona perfeitamente.

    O que não é ideal é que esse botão não receba a seta

    Você também acaba com dois botões Voltar no navegador IB, mas é fácil rotulá-lo para maior clareza.

    insira a descrição da imagem aqui

    Rápido

     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 } 

    Essa abordagem funcionou para mim (mas o botão “Voltar” não terá o sinal “< "):

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

    A resposta da @William está correta, no entanto, se o usuário iniciar um gesto de deslize para trás, o método viewWillDisappear será chamado e mesmo ‘self’ não estará na pilha de navegação (isto é, self.navigationController.viewControllers ganhou ‘t’ contém ‘self’), mesmo que o furto não seja concluído e o controlador de visualização não seja realmente exibido. Assim, a solução seria:

    1. Desativar o gesto swipe-to-go-back no viewDidAppear e permitir somente o uso do botão Voltar, usando:

       if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) { self.navigationController.interactivePopGestureRecognizer.enabled = NO; } 
    2. Ou simplesmente use viewDidDisappear, como 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. } } 

    Use isMovingFromParentViewController

     override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(true) if self.isMovingFromParentViewController { // current viewController is removed from parent // do some work } } 

    Swift 4 iOS 11.3 Versão:

    Isso se baseia na resposta do kgaidis de https://stackoverflow.com/a/34343418/4316579

    Não tenho certeza de quando a extensão parou de funcionar, mas, no momento em que escrevo (Swift 4), parece que a extensão não será mais executada, a menos que você declare a conformidade com UINavigationBarDelegate conforme descrito abaixo.

    Espero que isso ajude as pessoas que estão se perguntando por que sua extensão não funciona mais.

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

    Para interceptar o botão Voltar, basta cobri-lo com um UIControl transparente e interceptar os toques.

     @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 

    A solução que encontrei até agora não é muito boa, mas funciona para mim. Tomando essa resposta , também verifico se estou aparecendo de maneira programática ou não:

     - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; if ((self.isMovingFromParentViewController || self.isBeingDismissed) && !self.isPoppingProgrammatically) { // Do your stuff here } } 

    Você tem que adicionar essa propriedade ao seu controlador e configurá-lo para YES antes de aparecer programaticamente:

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

    Encontrou uma nova maneira de fazer isso:

    Objetivo-C

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

    Rápido

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

    Versão rápida da resposta do @ 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 } } 

    Agora, em qualquer controlador, basta estar em conformidade com RequestsNavigationPopVerification e esse comportamento é adotado por padrão.