Configuración de acción para el botón Atrás en el controlador de navegación

Estoy intentando sobrescribir la acción predeterminada del botón Atrás en un controlador de navegación. Le proporcioné a un objective una acción en el botón personalizado. Lo extraño es que al asignarlo a través del atributo de botón de retroceso no les presta atención y solo aparece la vista actual y vuelve a la raíz:

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

Tan pronto como lo configuro a través del leftBarButtonItem en el navigationItem llama a mi acción, sin embargo, luego el botón se ve como uno simple redondo en lugar del flechazo atrás:

 self.navigationItem.leftBarButtonItem = backButton; 

¿Cómo puedo hacer que llame a mi acción personalizada antes de volver a la vista raíz? ¿Hay alguna manera de sobrescribir la acción de respaldo predeterminada, o hay un método que siempre se llama al salir de una vista (viewDidUnload no hace eso)?

Intenta poner esto en el controlador de vista donde deseas detectar la prensa:

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

Implementé la extensión UIViewController-BackButtonHandler . No necesita subclasificar nada, simplemente colócalo en tu proyecto y anula el método navigationShouldPopOnBackButton en la clase 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 } 

Descargue la aplicación de muestra .

A diferencia de Amagrammer, dijo, es posible. Tienes que subclasificar tu navigationController. Expliqué todo aquí (incluido el código de ejemplo): http://www.hanspinckaers.com/custom-action-on-back-button-uinavigationcontroller

Versión Swift:

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

En su controlador de vista solo se ajusta a un protocolo y realiza cualquier acción que necesite:

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

Luego crea una clase, dijera NavigationController+BackButton , y simplemente copia y pega el siguiente código:

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

No es posible hacerlo directamente. Hay un par de alternativas:

  1. Cree su propio UIBarButtonItem personalizado que se valida al tocar y aparece si pasa la prueba
  2. Valide los contenidos del campo de formulario utilizando un método delegado UITextField , como -textFieldShouldReturn: que se -textFieldShouldReturn: después de presionar el botón Return o Done en el teclado

La desventaja de la primera opción es que no se puede acceder al estilo de flecha hacia la izquierda del botón Atrás desde un botón de barra personalizado. Entonces debes usar una imagen o ir con un botón de estilo regular.

La segunda opción es agradable porque recupera el campo de texto en el método de delegado, por lo que puede orientar su lógica de validación al campo de texto específico enviado al método de callback del delegado.

Por algunas razones de enhebrado, la solución mencionada por @HansPinckaers no era adecuada para mí, pero encontré una manera más fácil de tocar el botón Atrás, y quiero detallar esto aquí en caso de que esto pueda evitar horas de engaños para alguien más. El truco es realmente fácil: simplemente agrega un UIButton transparente como una subvista a tu UINavigationBar, y configura tus selectores para él como si fuera el botón real. Aquí hay un ejemplo usando Monotouch y C #, pero la traducción a Object-C no debería ser demasiado 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 } } 

Dato curioso: para fines de prueba y para encontrar buenas dimensiones para mi botón falso, configuré el color de fondo en azul … ¡Y se muestra detrás del botón Atrás! De todos modos, todavía capta cualquier toque que apunte al botón original.

Esta técnica le permite cambiar el texto del botón “Atrás” sin afectar el título de ninguno de los controles de vista o ver el texto del botón Atrás cambiar durante la animación.

Agregue esto al método init en el controlador de vista llamante :

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

La manera más fácil

Puede usar los métodos de delegado de UINavigationController. El método willShowViewController se llama cuando se presiona el botón Atrás de su VC. Haga lo que desee cuando vuelva a presionar btn

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

Encontré una solución que también conserva el estilo del botón Atrás. Agregue el siguiente método a su controlador de vista.

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

Ahora proporcione una funcionalidad según sea necesario en el siguiente método:

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

Todo lo que hace es cubrir el botón Atrás con un botón transparente;)

No creo que esto sea posible, fácilmente. La única forma en que creo para evitar esto es hacer tu propia imagen de flecha de botón hacia atrás para colocarla allí. Al principio fue frustrante para mí, pero veo por qué, por consistencia, se dejó de lado.

Puede acercarse (sin la flecha) creando un botón normal y ocultando el botón Atrás predeterminado:

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

Hay una manera más fácil de subclasificar el método de delegado de UINavigationBar y anular el método ShouldPopItem .

Aquí está mi solución Swift. En su subclase de UIViewController, anule el 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 } } 

La solución de onegray no es segura. De acuerdo con los documentos oficiales de Apple, https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html , deberíamos evitar hacerlo.

“Si el nombre de un método declarado en una categoría es igual a un método en la clase original, o un método en otra categoría en la misma clase (o incluso una superclase), el comportamiento no está definido en cuanto a qué método de implementación se usa en tiempo de ejecución. Es menos probable que sea un problema si está utilizando categorías con sus propias clases, pero puede causar problemas al usar categorías para agregar métodos a las clases estándar de Cocoa o Cocoa Touch “.

Usando Swift:

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

Aquí está la versión de Swift 3 de la respuesta de @oneway para capturar el evento del botón de retroceso de la barra de navegación antes de que se dispare. Como UINavigationBarDelegate no se puede usar para UIViewController , debe crear un delegado que se activará cuando se shouldPop navigationBar shouldPop .

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

Y luego, en su controlador de vista, agregue la función de delegado:

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

Me di cuenta de que a menudo queremos agregar un controlador de alerta para que los usuarios decidan si quieren regresar. Si es así, siempre puede return false en la función navigationShouldPopOnBackButton() y cerrar su controlador de vista haciendo algo como esto:

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

Al utilizar las variables de objective y acción que actualmente está dejando ‘nil’, debería poder cablear sus diálogos de guardado para que se llamen cuando el botón está “seleccionado”. Cuidado, esto puede activarse en momentos extraños.

Estoy de acuerdo principalmente con Amagrammer, pero no creo que sea tan difícil hacer el botón con la flecha personalizada. Simplemente cambiaría el nombre del botón Atrás, tomaría una captura de pantalla, photoshop el tamaño del botón necesario, y tendría que ser la imagen en la parte superior de su botón.

Puede intentar acceder al elemento del botón derecho de NavigationBars y establecer su propiedad de selector … aquí está una referencia UIBarButtonItem referencia , otra cosa si este doenst trabajo que def funcionar es, establecer el elemento del botón derecho de la barra de navegación a un UIBarButtonItem personalizado que usted crea y establece su selector … espero que esto ayude

Para un formulario que requiera la entrada del usuario de esta manera, recomendaría invocarlo como un “modal” en lugar de una parte de su stack de navegación. De esta forma, deben ocuparse de los negocios en el formulario, luego puede validarlo y descartarlo mediante un botón personalizado. Incluso puede diseñar una barra de navegación que se ve igual que el rest de su aplicación, pero le da más control.

Al menos en Xcode 5, hay una solución simple y bastante buena (no perfecta). En IB, arrastre un elemento de botón de barra fuera del panel de Utilidades y suéltelo en el lado izquierdo de la barra de navegación donde estaría el botón Atrás. Establezca la etiqueta en “Atrás”. Tendrás un botón de funcionamiento que puedes vincular a tu IBAction y cerrar tu viewController. Estoy haciendo un trabajo y luego desencadenar un ciclo de desenrollado y funciona perfectamente.

Lo que no es ideal es que este botón no obtenga la

También termina con dos botones Atrás en el navegador IB, pero es bastante fácil etiquetarlo para mayor claridad.

enter image description here

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 } 

Este enfoque funcionó para mí (pero el botón “Atrás” no tendrá el signo “<"):

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

La respuesta de @William es correcta, sin embargo, si el usuario inicia un gesto de deslizar para volver, se llama al método viewWillDisappear e incluso ‘self’ no estará en la stack de navegación (es decir, self.navigationController.viewControllers won ‘t contiene’ self ‘), incluso si el deslizamiento no se completa y el controlador de vista no se ha reventado realmente. Por lo tanto, la solución sería:

  1. Deshabilite el gesto de deslizar para volver atrás en viewDidAppear y solo permita el uso del botón Atrás, usando:

     if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) { self.navigationController.interactivePopGestureRecognizer.enabled = NO; } 
  2. O simplemente use viewDidDisappear en su lugar, de la siguiente manera:

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

Utilice 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 Version:

Esto se basa en la respuesta de kgaidis de https://stackoverflow.com/a/34343418/4316579

No estoy seguro de cuándo la extensión dejó de funcionar, pero en el momento de escribir esto (Swift 4), parece que la extensión ya no se ejecutará a menos que declare la conformidad de UINavigationBarDelegate como se describe a continuación.

Espero que esto ayude a las personas que se preguntan por qué su extensión ya no funciona.

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

Para interceptar el botón Atrás, simplemente cúbrelo con un UIControl transparente e intercepta los 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 

La solución que he encontrado hasta ahora no es muy buena, pero funciona para mí. Tomando esta respuesta , también compruebo si estoy apareciendo programáticamente o no:

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

Debe agregar esa propiedad a su controlador y configurarlo en SÍ antes de hacer clic en progtwigción:

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

Encontré una nueva forma de hacerlo:

C objective

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

Rápido

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

Versión Swift de la respuesta de @ 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 } } 

Ahora, en cualquier controlador, simplemente cumpla con RequestsNavigationPopVerification y este comportamiento se adopta de forma predeterminada.