Al cambiar el botón Atrás en iOS 7, se desactiva el deslizamiento para navegar de nuevo

Tengo una aplicación para iOS 7 en la que estoy configurando un botón de retroceso personalizado como este:

UIImage *backButtonImage = [UIImage imageNamed:@"back-button"]; UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom]; [backButton setImage:backButtonImage forState:UIControlStateNormal]; backButton.frame = CGRectMake(0, 0, 20, 20); [backButton addTarget:self action:@selector(popViewController) forControlEvents:UIControlEventTouchUpInside]; UIBarButtonItem *backBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton]; viewController.navigationItem.leftBarButtonItem = backBarButtonItem; 

Pero esto desactiva el gesto de “deslizar de izquierda a derecha” de iOS 7 para navegar al controlador anterior. ¿Alguien sabe cómo puedo configurar un botón personalizado y aún mantener este gesto habilitado?

EDITAR: Intenté establecer viewController.navigationItem.backBarButtonItem, pero esto no parece mostrar mi imagen personalizada.

IMPORTANTE: Este es un truco. Recomendaría echarle un vistazo a esta respuesta .

Llamar a la siguiente línea después de asignar el leftBarButtonItem funcionó para mí:

 self.navigationController.interactivePopGestureRecognizer.delegate = self; 

Editar: Esto no funciona si se llama en los métodos init . Debería llamarse en viewDidLoad o métodos similares.

Utilice las propiedades backIndicatorImage y backIndicatorTransitionMaskImage de UINavigationBar si es posible. Al configurar estos en un UIAppearanceProxy puede modificar fácilmente el comportamiento en su aplicación. La cuestión es que solo puedes configurarlos en iOS 7, pero eso funciona porque solo puedes usar el gesto pop en ios 7 de todos modos. Tu estilo normal de ios 6 puede permanecer intacto.

 UINavigationBar* appearanceNavigationBar = [UINavigationBar appearance]; //the appearanceProxy returns NO, so ask the class directly if ([[UINavigationBar class] instancesRespondToSelector:@selector(setBackIndicatorImage:)]) { appearanceNavigationBar.backIndicatorImage = [UIImage imageNamed:@"back"]; appearanceNavigationBar.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"back"]; //sets back button color appearanceNavigationBar.tintColor = [UIColor whiteColor]; }else{ //do ios 6 customization } 

Intentar manipular el delegado de RePictureRecognizer interactivo dará lugar a una gran cantidad de problemas.

Vi esta solución http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/ qué subclases UINavigationController. Es una solución mejor, ya que maneja el caso en el que deslizas antes de que el controlador esté en su lugar, lo que causa un locking.

Además de esto, me di cuenta de que si hace un deslizamiento en el controlador de vista raíz (después de presionar uno y viceversa), la UI deja de responder (también el mismo problema en la respuesta anterior).

Entonces, el código en el UINavigationController subclasificado debería verse así:

 @implementation NavigationController - (void)viewDidLoad { [super viewDidLoad]; __weak NavigationController *weakSelf = self; if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) { self.interactivePopGestureRecognizer.delegate = weakSelf; self.delegate = weakSelf; } } - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated { // Hijack the push method to disable the gesture if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) { self.interactivePopGestureRecognizer.enabled = NO; } [super pushViewController:viewController animated:animated]; } #pragma mark - UINavigationControllerDelegate - (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animate { // Enable the gesture again once the new controller is shown self.interactivePopGestureRecognizer.enabled = ([self respondsToSelector:@selector(interactivePopGestureRecognizer)] && [self.viewControllers count] > 1); } @end 

yo suelo

 [[UINavigationBar appearance] setBackIndicatorImage:[UIImage imageNamed:@"nav_back.png"]]; [[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"nav_back.png"]]; [UIBarButtonItem.appearance setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -64) forBarMetrics:UIBarMetricsDefault]; 

También escondo el botón Atrás, reemplazándolo con un leftBarItem personalizado.
Eliminar el delegado de interactivePopGestureRecognizer después de que la acción de inserción me funcionara:

 [self.navigationController pushViewController:vcToPush animated:YES]; // Enabling iOS 7 screen-edge-pan-gesture for pop action if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) { self.navigationController.interactivePopGestureRecognizer.delegate = nil; } 
 navigationController.interactivePopGestureRecognizer.delegate = (id)self; 

Esto es de http://stuartkhall.com/posts/ios-7-development-tips-tricks-hacks , pero causa varios errores:

  1. Empuje otro viewController dentro del navigationController cuando deslice hacia adentro desde el borde izquierdo de la pantalla;
  2. O bien, deslice el dedo desde el borde izquierdo de la pantalla cuando aparezca el control superior ViewController desde navigationController;

Por ejemplo, cuando se muestre el controlador raízViewController de navigationController, deslícese hacia adentro desde el borde izquierdo de la pantalla y toque algo (RÁPIDO) para insertar otro ViewController en el control de navegación, luego

  • El rootViewController no responde ningún evento táctil;
  • El otro ViewController no se mostrará;
  • Pase el dedo desde el borde de la pantalla nuevamente, se mostrará el otro ViewController;
  • Toque el botón Atrás personalizado para abrir el otro ViewController, crash!

Por lo tanto, debe implementar el método self.navigationController.interactivePopGestureRecognizer.delegate en self.navigationController.interactivePopGestureRecognizer.delegate esta manera:

 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { if (gestureRecognizer == navigationController.interactivePopGestureRecognizer) { return !navigationController.<#TODO: isPushAnimating#> && [navigationController.viewControllers count] > 1; } return YES; } 

Aquí está la versión swift3 de la respuesta de Nick H247

 class NavigationController: UINavigationController { override func viewDidLoad() { super.viewDidLoad() if responds(to: #selector(getter: interactivePopGestureRecognizer)) { interactivePopGestureRecognizer?.delegate = self delegate = self } } override func pushViewController(_ viewController: UIViewController, animated: Bool) { if responds(to: #selector(getter: interactivePopGestureRecognizer)) { interactivePopGestureRecognizer?.isEnabled = false } super.pushViewController(viewController, animated: animated) } } extension NavigationController: UINavigationControllerDelegate { func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { interactivePopGestureRecognizer?.isEnabled = (responds(to: #selector(getter: interactivePopGestureRecognizer)) && viewControllers.count > 1) } } extension NavigationController: UIGestureRecognizerDelegate {} 

Pruebe self.navigationController. interactivePopGestureRecognizer .enabled = YES;

No escribí esto, pero el siguiente blog me ayudó mucho y resolvió mis problemas con el botón de navegación personalizado:

http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/

En resumen, implementa un UINavigationController personalizado que usa el delegado de gestos pop. ¡Muy limpio y portátil!

Código:

 @interface CBNavigationController : UINavigationController  @end @implementation CBNavigationController - (void)viewDidLoad { __weak CBNavigationController *weakSelf = self; if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) { self.interactivePopGestureRecognizer.delegate = weakSelf; self.delegate = weakSelf; } } // Hijack the push method to disable the gesture - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated { if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) self.interactivePopGestureRecognizer.enabled = NO; [super pushViewController:viewController animated:animated]; } #pragma mark UINavigationControllerDelegate - (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animate { // Enable the gesture again once the new controller is shown if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) self.interactivePopGestureRecognizer.enabled = YES; } 

Editar. Solución adicional para los problemas cuando un usuario intenta deslizar hacia la izquierda en un controlador de vista raíz:

 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)] && self.topViewController == [self.viewControllers firstObject] && gestureRecognizer == self.interactivePopGestureRecognizer) { return NO; } return YES; } 

RootView

 override func viewDidAppear(_ animated: Bool) { self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false } 

ChildView

 override func viewDidLoad() { self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true self.navigationController?.interactivePopGestureRecognizer?.delegate = self } extension ChildViewController: UIGestureRecognizerDelegate {} 

Tuve un problema similar cuando estaba asignando el controlador de vista actual como el delegado para el gesto pop interactivo, pero rompería el gesto en cualquier vista que se presionara, o vistas debajo de la vista en la stack de navegación. La forma en que resolví esto fue configurar el delegado en -viewDidAppear , luego establecerlo en nil en -viewWillDisappear . Eso permitió que mis otros puntos de vista funcionaran correctamente.

Imagina que estamos usando la plantilla de proyecto de maestro / detalle predeterminada de Apple, donde el maestro es un controlador de vista de tabla y al tocarlo se mostrará el controlador de vista detallada.

Queremos personalizar el botón Atrás que aparece en el controlador de vista detallada. Esta es la forma de personalizar la imagen , el color de la imagen , el texto , el color del texto y la fuente del botón Atrás.


Para cambiar la imagen, el color de la imagen, el color del texto o la fuente de forma global, coloque lo siguiente en una ubicación que se llame antes de crear cualquiera de sus controladores de visualización (por ejemplo, application:didFinishLaunchingWithOptions: es un buen lugar).

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { UINavigationBar* navigationBarAppearance = [UINavigationBar appearance]; // change the back button, using default tint color navigationBarAppearance.backIndicatorImage = [UIImage imageNamed:@"back"]; navigationBarAppearance.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"back"]; // change the back button, using the color inside the original image navigationBarAppearance.backIndicatorImage = [[UIImage imageNamed:@"back"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; navigationBarAppearance.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"back"]; // change the tint color of everything in a navigation bar navigationBarAppearance.tintColor = [UIColor greenColor]; // change the font in all toolbar buttons NSDictionary *barButtonTitleTextAttributes = @{ NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue-Light" size:12.0], NSForegroundColorAttributeName: [UIColor purpleColor] }; [[UIBarButtonItem appearance] setTitleTextAttributes:barButtonTitleTextAttributes forState:UIControlStateNormal]; return YES; } 

Tenga en cuenta que puede usar appearanceWhenContainedIn: para tener más control sobre qué controladores de vista se ven afectados por estos cambios, pero tenga en cuenta que no puede pasar [DetailViewController class] , porque está dentro de un UINavigationController, no de DetailViewController. Esto significa que tendrá que subclase UINavigationController si desea tener más control sobre lo que se ve afectado.

Para personalizar el texto o la fuente / color de un elemento específico del botón de retroceso, debe hacerlo en MasterViewController (¡no en DetailViewController!). Esto parece poco intuitivo porque el botón aparece en DetailViewController. Sin embargo, una vez que comprenda que la forma de personalizarlo es establecer una propiedad en un elemento de navegación, comienza a tener más sentido.

 - (void)viewDidLoad { // MASTER view controller [super viewDidLoad]; UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithTitle:@"Testing" style:UIBarButtonItemStylePlain target:nil action:nil]; NSDictionary *barButtonTitleTextAttributes = @{ NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue-Light" size:12.0], NSForegroundColorAttributeName: [UIColor purpleColor] }; [buttonItem setTitleTextAttributes:barButtonTitleTextAttributes forState:UIControlStateNormal]; self.navigationItem.backBarButtonItem = buttonItem; } 

Nota: intentar establecer titleTextAttributes después de configurar self.navigationItem.backBarButtonItem no parece funcionar, por lo que deben establecerse antes de asignar el valor a esta propiedad.

Cree una clase ‘TTNavigationViewController’ que es la subclase de ‘UINavigationController’ y convierta su controlador de navegación existente de esta clase en guión gráfico / clase, código de ejemplo en clase –

  class TTNavigationViewController: UINavigationController, UIGestureRecognizerDelegate { override func viewDidLoad() { super.viewDidLoad() self.setNavigationBarHidden(true, animated: false) // enable slide-back if self.responds(to: #selector(getter: UINavigationController.interactivePopGestureRecognizer)) { self.interactivePopGestureRecognizer?.isEnabled = true self.interactivePopGestureRecognizer?.delegate = self } } func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { return true }}