ios - prefer - swift navigation bar large title
Sin deslizar hacia atrás cuando se oculta la barra de navegación en UINavigationController (14)
Me encanta el paquete de deslizamiento que se hereda de incrustar tus vistas en un UINavigationController. Lamentablemente, parece que no puedo encontrar la forma de ocultar la barra de navegación, pero aún tengo el gesto toque deslizar hacia atrás. Puedo escribir gestos personalizados pero prefiero no hacerlo y confiar en el gesto de deslizamiento hacia atrás del UINavigationController.
si lo desactivo en el guión gráfico, el deslizamiento hacia atrás no funciona
alternativamente si lo oculto programáticamente, el mismo escenario.
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController setNavigationBarHidden:YES animated:NO]; // and animated:YES
}
¿No hay forma de ocultar la barra de navegación superior y todavía tener el deslizamiento?
Xamarin Respuesta:
Implemente la interfaz IUIGestureRecognizerDelegate
en la definición de Class de su ViewController:
public partial class myViewController : UIViewController, IUIGestureRecognizerDelegate
En su ViewController agregue el siguiente método:
[Export("gestureRecognizerShouldBegin:")]
public bool ShouldBegin(UIGestureRecognizer recognizer) {
if (recognizer is UIScreenEdgePanGestureRecognizer &&
NavigationController.ViewControllers.Length == 1) {
return false;
}
return true;
}
En su ViewController ViewDidLoad()
agregue la siguiente línea:
NavigationController.InteractivePopGestureRecognizer.Delegate = this;
Problemas con otros métodos
Configurar el interactivePopGestureRecognizer.delegate = nil
tiene efectos secundarios no deseados.
Configurar navigationController?.navigationBar.hidden = true
funciona, pero no permite ocultar su cambio en la barra de navegación.
Por último, generalmente es una mejor práctica crear un objeto modelo que sea UIGestureRecognizerDelegate
para su controlador de navegación. Configurarlo en un controlador en la pila UINavigationController
es lo que causa los errores EXC_BAD_ACCESS
.
Solución completa
Primero, agrega esta clase a tu proyecto:
class InteractivePopRecognizer: NSObject, UIGestureRecognizerDelegate {
var navigationController: UINavigationController
init(controller: UINavigationController) {
self.navigationController = controller
}
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return navigationController.viewControllers.count > 1
}
// This is necessary because without it, subviews of your top controller can
// cancel out your gesture recognizer on the edge.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
Luego, establezca elPopGestureRecognizer.delegate interactivePopGestureRecognizer.delegate
su controlador de navegación en una instancia de su nueva clase InteractivePopRecognizer
.
var popRecognizer: InteractivePopRecognizer?
override func viewDidLoad() {
super.viewDidLoad()
setInteractiveRecognizer()
}
private func setInteractiveRecognizer() {
guard let controller = navigationController else { return }
popRecognizer = InteractivePopRecognizer(controller: controller)
controller.interactivePopGestureRecognizer?.delegate = popRecognizer
}
Disfrute de una barra de navegación oculta sin efectos secundarios, que funciona incluso si su controlador superior tiene tablas, colecciones o subvistas de vista de desplazamiento.
Aprovechando la respuesta de Hunter Maximillion Monk , hice una subclase para UINavigationController y luego establecí la clase personalizada para mi UINavigationController en mi guión gráfico. El código final para las dos clases se ve así:
InteractivePopRecognizer:
class InteractivePopRecognizer: NSObject {
// MARK: - Properties
fileprivate weak var navigationController: UINavigationController?
// MARK: - Init
init(controller: UINavigationController) {
self.navigationController = controller
super.init()
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
}
}
extension InteractivePopRecognizer: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return (navigationController?.viewControllers.count ?? 0) > 1
}
// This is necessary because without it, subviews of your top controller can cancel out your gesture recognizer on the edge.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
HiddenNavBarNavigationController:
class HiddenNavBarNavigationController: UINavigationController {
// MARK: - Properties
private var popRecognizer: InteractivePopRecognizer?
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupPopRecognizer()
}
// MARK: - Setup
private func setupPopRecognizer() {
popRecognizer = InteractivePopRecognizer(controller: self)
}
}
Storyboard:
Aquí se explica cómo desactivar el reconocedor de gestos cuando el usuario se desliza fuera del ViewController. Puede pegarlo en su viewWillAppear () o en sus métodos ViewDidLoad ().
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
Descubrí que otras soluciones publicadas que reemplazaban al delegado o que lo establecían en cero ocasionaron un comportamiento inesperado.
En mi caso, cuando estaba en la parte superior de la pila de navegación y traté de usar el gesto para hacer estallar uno más, fallaba (como se esperaba), pero los intentos posteriores de empujar hacia la pila comenzarían a causar extraños errores gráficos en el barra de navegación. Esto tiene sentido, porque el delegado se usa para manejar algo más que si se bloquea o no el gesto para reconocer cuando se oculta la barra de navegación, y todo ese otro comportamiento se descarta.
A partir de mis pruebas, parece que gestureRecognizer(_:, shouldReceiveTouch:)
es el método que el delegado original está implementando para gestureRecognizer(_:, shouldReceiveTouch:)
que el gesto se reconozca cuando la barra de navegación está oculta, no gestureRecognizerShouldBegin(_:)
. Otras soluciones que implementan gestureRecognizerShouldBegin(_:)
en su trabajo delegado porque la falta de una implementación de gestureRecognizer(_:, shouldReceiveTouch:)
causará el comportamiento predeterminado de recibir todos los toques.
La solución de @Nathan Perry se acerca, pero sin una implementación de respondsToSelector(_:)
, el código UIKit que envía mensajes al delegado creerá que no hay implementación para ninguno de los otros métodos delegados, y forwardingTargetForSelector(_:)
nunca obtendrá llamado.
Por lo tanto, tomamos el control de `gestureRecognizer (_ :, shouldReceiveTouch :) en el escenario específico en el que queremos modificar el comportamiento y, de lo contrario, reenviamos todo lo demás al delegado.
import Foundation
class AlwaysPoppableNavigationController : UINavigationController {
private var alwaysPoppableDelegate: AlwaysPoppableDelegate!
override func viewDidLoad() {
super.viewDidLoad()
self.alwaysPoppableDelegate = AlwaysPoppableDelegate(navigationController: self, originalDelegate: self.interactivePopGestureRecognizer!.delegate!)
self.interactivePopGestureRecognizer!.delegate = self.alwaysPoppableDelegate
}
}
private class AlwaysPoppableDelegate : NSObject, UIGestureRecognizerDelegate {
weak var navigationController: AlwaysPoppableNavigationController?
var originalDelegate: UIGestureRecognizerDelegate
init(navigationController: AlwaysPoppableNavigationController, originalDelegate: UIGestureRecognizerDelegate) {
self.navigationController = navigationController
self.originalDelegate = originalDelegate
}
@objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if let nav = navigationController, nav.isNavigationBarHidden && nav.viewControllers.count > 1 { {
return true
}
else {
return self.originalDelegate.gestureRecognizer!(gestureRecognizer, shouldReceive: touch)
}
}
override func responds(to aSelector: Selector) -> Bool {
if aSelector == #selector(gestureRecognizer(_:shouldReceive:)) {
return true
}
else {
return self.originalDelegate.responds(to: aSelector)
}
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
return self.originalDelegate
}
}
En mi caso, para evitar efectos extraños
Controlador de vista raíz
override func viewDidLoad() {
super.viewDidLoad()
// Enable swipe back when no navigation bar
navigationController?.interactivePopGestureRecognizer?.delegate = self
}
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if(navigationController!.viewControllers.count > 1){
return true
}
return false
}
http://www.gampood.com/pop-viewcontroller-with-out-navigation-bar/
En mi vista, el controlador sin la barra de navegación que uso
open override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
CATransaction.begin()
UIView.animate(withDuration: 0.25, animations: { [weak self] in
self?.navigationController?.navigationBar.alpha = 0.01
})
CATransaction.commit()
}
open override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
CATransaction.begin()
UIView.animate(withDuration: 0.25, animations: { [weak self] in
self?.navigationController?.navigationBar.alpha = 1.0
})
CATransaction.commit()
}
Sin embargo, durante el descarte interactivo, el botón de retroceso brillará, razón por la cual lo oculté.
Intenté esto y está funcionando perfectamente: cómo ocultar la barra de navegación sin perder la capacidad de deslizar hacia atrás
La idea es implementar "UIGestureRecognizerDelegate" en su .h y agregar esto a su archivo .m.
- (void)viewWillAppear:(BOOL)animated {
// hide nav bar
[[self navigationController] setNavigationBarHidden:YES animated:YES];
// enable slide-back
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
self.navigationController.interactivePopGestureRecognizer.delegate = self;
}
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
return YES;
}
Mi solución es extender directamente la clase UINavigationController
:
import UIKit
extension UINavigationController: UIGestureRecognizerDelegate {
override open func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.interactivePopGestureRecognizer?.delegate = self
}
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return self.viewControllers.count > 1
}
}
De esta forma, todos los controles de navegación serán descartables deslizando.
Parece que la solución provista por @ChrisVasseli es la mejor. Me gustaría proporcionar la misma solución en Objective-C porque la pregunta es sobre Objective-C (ver etiquetas)
@interface InteractivePopGestureDelegate : NSObject <UIGestureRecognizerDelegate>
@property (nonatomic, weak) UINavigationController *navigationController;
@property (nonatomic, weak) id<UIGestureRecognizerDelegate> originalDelegate;
@end
@implementation InteractivePopGestureDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if (self.navigationController.navigationBarHidden && self.navigationController.viewControllers.count > 1) {
return YES;
} else {
return [self.originalDelegate gestureRecognizer:gestureRecognizer shouldReceiveTouch:touch];
}
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
if (aSelector == @selector(gestureRecognizer:shouldReceiveTouch:)) {
return YES;
} else {
return [self.originalDelegate respondsToSelector:aSelector];
}
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return self.originalDelegate;
}
@end
@interface NavigationController ()
@property (nonatomic) InteractivePopGestureDelegate *interactivePopGestureDelegate;
@end
@implementation NavigationController
- (void)viewDidLoad
{
[super viewDidLoad];
self.interactivePopGestureDelegate = [InteractivePopGestureDelegate new];
self.interactivePopGestureDelegate.navigationController = self;
self.interactivePopGestureDelegate.originalDelegate = self.interactivePopGestureRecognizer.delegate;
self.interactivePopGestureRecognizer.delegate = self.interactivePopGestureDelegate;
}
@end
Puede subclase UINavigationController de la siguiente manera:
@interface CustomNavigationController : UINavigationController<UIGestureRecognizerDelegate>
@end
Implementación:
@implementation CustomNavigationController
- (void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated {
[super setNavigationBarHidden:hidden animated:animated];
self.interactivePopGestureRecognizer.delegate = self;
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if (self.viewControllers.count > 1) {
return YES;
}
return NO;
}
@end
Puedes hacerlo con un Delegado Proxy. Cuando está construyendo el controlador de navegación, tome el delegado existente. Y pasarlo al proxy. A continuación, pase todos los métodos de delegado al delegado existente, excepto gestureRecognizer:shouldReceiveTouch:
using forwardingTargetForSelector:
Preparar:
let vc = UIViewController(nibName: nil, bundle: nil)
let navVC = UINavigationController(rootViewController: vc)
let bridgingDelegate = ProxyDelegate()
bridgingDelegate.existingDelegate = navVC.interactivePopGestureRecognizer?.delegate
navVC.interactivePopGestureRecognizer?.delegate = bridgingDelegate
Delegado Proxy:
class ProxyDelegate: NSObject, UIGestureRecognizerDelegate {
var existingDelegate: UIGestureRecognizerDelegate? = nil
override func forwardingTargetForSelector(aSelector: Selector) -> AnyObject? {
return existingDelegate
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
return true
}
}
Un truco que está funcionando es configurar el delegado del UINavigationController
en nil
esta manera:
[self.navigationController.interactivePopGestureRecognizer setDelegate:nil];
Pero en algunas situaciones podría crear efectos extraños.
Algunas personas han tenido éxito al invocar el método setNavigationBarHidden
con YES
animado en su lugar.