Actualizado para iOS 13.4
iOS 13.4 rompió la solución anterior, por lo que las cosas se pondrán feas. Parece que en iOS 13.4 este comportamiento ahora está controlado por un método privado _gestureRecognizer:shouldReceiveEvent:
(no debe confundirse con el nuevo shouldReceive
método público agregado en iOS 13.4).
Descubrí que otras soluciones publicadas que anulaban al delegado o lo configuraban como nulo causaron un comportamiento inesperado.
En mi caso, cuando estaba en la parte superior de la pila de navegación e intenté usar el gesto para hacer estallar uno más, fallaría (como se esperaba), pero los intentos posteriores de empujar hacia la pila comenzarían a causar fallas gráficas extrañas en el barra de navegación. Esto tiene sentido, porque el delegado se está utilizando para manejar más que solo si se debe bloquear o no el gesto para que no se reconozca cuando la barra de navegación está oculta y todo ese otro comportamiento se descarta.
Según mis pruebas, parece que gestureRecognizer(_:, shouldReceiveTouch:)
es el método que está implementando el delegado original para evitar que el gesto sea reconocido cuando la barra de navegación está oculta, no gestureRecognizerShouldBegin(_:)
. Otras soluciones que implementan gestureRecognizerShouldBegin(_:)
en su trabajo delegado debido a la falta de una implementación degestureRecognizer(_:, shouldReceiveTouch:)
provocará 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 de delegado, yforwardingTargetForSelector(_:)
nunca será 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, reenviar todo lo demás al delegado.
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?
weak 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 navigationController = navigationController, navigationController.isNavigationBarHidden && navigationController.viewControllers.count > 1 {
return true
}
else if let originalDelegate = originalDelegate {
return originalDelegate.gestureRecognizer!(gestureRecognizer, shouldReceive: touch)
}
else {
return false
}
}
@objc func _gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceiveEvent event: UIEvent) -> Bool {
if let navigationController = navigationController, navigationController.isNavigationBarHidden && navigationController.viewControllers.count > 1 {
return true
}
else if let originalDelegate = originalDelegate {
let selector = #selector(_gestureRecognizer(_:shouldReceiveEvent:))
if originalDelegate.responds(to: selector) {
let result = originalDelegate.perform(selector, with: gestureRecognizer, with: event)
return result != nil
}
}
return false
}
override func responds(to aSelector: Selector) -> Bool {
if #available(iOS 13.4, *) {
return originalDelegate?.responds(to: aSelector) ?? false
}
else {
if aSelector == #selector(gestureRecognizer(_:shouldReceive:)) {
return true
}
else {
return originalDelegate?.responds(to: aSelector) ?? false
}
}
}
override func forwardingTarget(for aSelector: Selector) -> Any? {
if #available(iOS 13.4, *), aSelector == #selector(_gestureRecognizer(_:shouldReceiveEvent:)) {
return nil
}
else {
return self.originalDelegate
}
}
}