Aktualisiert für iOS 13.4
iOS 13.4 hat die vorherige Lösung gebrochen, daher werden die Dinge hässlich. Es sieht so aus, als würde dieses Verhalten in iOS 13.4 jetzt von einer privaten Methode gesteuert _gestureRecognizer:shouldReceiveEvent:
(nicht zu verwechseln mit der neuen öffentlichen shouldReceive
Methode, die in iOS 13.4 hinzugefügt wurde).
Ich stellte fest, dass andere veröffentlichte Lösungen, die den Delegaten überschrieben oder auf Null setzten, ein unerwartetes Verhalten verursachten.
In meinem Fall schlug es (wie erwartet) fehl, wenn ich mich oben auf dem Navigationsstapel befand und versuchte, mit der Geste eine weitere zu öffnen, aber nachfolgende Versuche, auf den Stapel zu drücken, verursachten seltsame grafische Störungen im Navigationsleiste. Dies ist sinnvoll, da der Delegat nicht nur verwendet wird, um zu verhindern, dass die Geste erkannt wird, wenn die Navigationsleiste ausgeblendet ist, und das gesamte andere Verhalten verworfen wird.
Aus meinen Tests geht hervor, dass dies gestureRecognizer(_:, shouldReceiveTouch:)
die Methode ist, die der ursprüngliche Delegat implementiert, um zu verhindern, dass die Geste erkannt wird, wenn die Navigationsleiste ausgeblendet ist, nicht gestureRecognizerShouldBegin(_:)
. Andere Lösungen, die gestureRecognizerShouldBegin(_:)
in ihrer Delegiertenarbeit implementiert gestureRecognizer(_:, shouldReceiveTouch:)
werden, weil das Fehlen einer Implementierung von das Standardverhalten beim Empfangen aller Berührungen verursacht.
Die Lösung von @Nathan Perry kommt nahe, aber ohne eine Implementierung von respondsToSelector(_:)
wird der UIKit-Code, der Nachrichten an den Delegierten sendet, glauben, dass es keine Implementierung für eine der anderen Delegatenmethoden gibt, undforwardingTargetForSelector(_:)
wird niemals aufgerufen.
Wir übernehmen also die Kontrolle über `gestureRecognizer (_:, shouldReceiveTouch :) in dem einen bestimmten Szenario, in dem wir das Verhalten ändern möchten, und leiten ansonsten alles andere an den Delegaten weiter.
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
}
// For handling iOS before 13.4
@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
}
}
// For handling iOS 13.4+
@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, *) {
// iOS 13.4+ does not need to override responds(to:) behavior, it only uses forwardingTarget
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
}
}
}