Vielleicht ist es etwas zu spät, aber ich wollte das gleiche Verhalten auch schon früher. Und die Lösung, mit der ich mich entschieden habe, funktioniert in einer der Apps, die derzeit im App Store erhältlich sind, recht gut. Da ich noch niemanden gesehen habe, der mit einer ähnlichen Methode arbeitet, möchte ich sie hier teilen. Der Nachteil dieser Lösung ist, dass eine Unterklasse erforderlich ist UINavigationController
. Obwohl die Verwendung von Method Swizzling helfen könnte, dies zu vermeiden, bin ich nicht so weit gegangen.
Die Standard-Zurück-Schaltfläche wird also tatsächlich von verwaltet UINavigationBar
. Wenn ein Benutzer auf die Schaltfläche "Zurück" tippt, UINavigationBar
fragen Sie seinen Delegierten, ob er UINavigationItem
bei einem Anruf die Spitze öffnen soll navigationBar(_:shouldPop:)
. UINavigationController
implementieren Sie dies tatsächlich, aber es erklärt nicht öffentlich, dass es annimmt UINavigationBarDelegate
(warum!?). Um dieses Ereignis abzufangen, erstellen Sie eine Unterklasse von UINavigationController
, deklarieren Sie deren Konformität mit UINavigationBarDelegate
und implementieren Sie sie navigationBar(_:shouldPop:)
. Geben Sie zurück, true
wenn der oberste Gegenstand geknallt werden soll. Rückkehrfalse
wenn es bleiben soll.
Es gibt zwei Probleme. Das erste ist, dass Sie die UINavigationController
Version von navigationBar(_:shouldPop:)
irgendwann aufrufen müssen . Erklärt es UINavigationBarController
aber nicht öffentlich als konformUINavigationBarDelegate
, führt der Versuch, es aufzurufen, zu einem Fehler bei der Kompilierung. Die Lösung, die ich gewählt habe, besteht darin, die Objective-C-Laufzeit zu verwenden, um die Implementierung direkt abzurufen und aufzurufen. Bitte lassen Sie mich wissen, wenn jemand eine bessere Lösung hat.
Das andere Problem ist, dass navigationBar(_:shouldPop:)
zuerst aufgerufen wird, popViewController(animated:)
wenn der Benutzer auf die Zurück-Schaltfläche tippt. Die Reihenfolge wird umgekehrt, wenn der View Controller durch Aufrufen geöffnet wird popViewController(animated:)
. In diesem Fall verwende ich einen Booleschen Wert, um festzustellen, ob popViewController(animated:)
zuvor aufgerufen wurde, navigationBar(_:shouldPop:)
was bedeutet, dass der Benutzer auf die Schaltfläche "Zurück" getippt hat.
Außerdem mache ich eine Erweiterung von UIViewController
, damit der Navigations-Controller den Ansichts-Controller fragt, ob er geöffnet werden soll, wenn der Benutzer auf die Zurück-Schaltfläche tippt. View Controller können zurückkehren false
und alle erforderlichen Aktionen ausführen und popViewController(animated:)
später aufrufen .
class InterceptableNavigationController: UINavigationController, UINavigationBarDelegate {
// If a view controller is popped by tapping on the back button, `navigationBar(_:, shouldPop:)` is called first follows by `popViewController(animated:)`.
// If it is popped by calling to `popViewController(animated:)`, the order reverses and we need this flag to check that.
private var didCallPopViewController = false
override func popViewController(animated: Bool) -> UIViewController? {
didCallPopViewController = true
return super.popViewController(animated: animated)
}
func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
// If this is a subsequence call after `popViewController(animated:)`, we should just pop the view controller right away.
if didCallPopViewController {
return originalImplementationOfNavigationBar(navigationBar, shouldPop: item)
}
// The following code is called only when the user taps on the back button.
guard let vc = topViewController, item == vc.navigationItem else {
return false
}
if vc.shouldBePopped(self) {
return originalImplementationOfNavigationBar(navigationBar, shouldPop: item)
} else {
return false
}
}
func navigationBar(_ navigationBar: UINavigationBar, didPop item: UINavigationItem) {
didCallPopViewController = false
}
/// Since `UINavigationController` doesn't publicly declare its conformance to `UINavigationBarDelegate`,
/// trying to called `navigationBar(_:shouldPop:)` will result in a compile error.
/// So, we'll have to use Objective-C runtime to directly get super's implementation of `navigationBar(_:shouldPop:)` and call it.
private func originalImplementationOfNavigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
let sel = #selector(UINavigationBarDelegate.navigationBar(_:shouldPop:))
let imp = class_getMethodImplementation(class_getSuperclass(InterceptableNavigationController.self), sel)
typealias ShouldPopFunction = @convention(c) (AnyObject, Selector, UINavigationBar, UINavigationItem) -> Bool
let shouldPop = unsafeBitCast(imp, to: ShouldPopFunction.self)
return shouldPop(self, sel, navigationBar, item)
}
}
extension UIViewController {
@objc func shouldBePopped(_ navigationController: UINavigationController) -> Bool {
return true
}
}
Und in Ihren View-Controllern implementieren shouldBePopped(_:)
. Wenn Sie diese Methode nicht implementieren, wird standardmäßig der Ansichts-Controller geöffnet, sobald der Benutzer wie gewohnt auf die Schaltfläche "Zurück" tippt.
class MyViewController: UIViewController {
override func shouldBePopped(_ navigationController: UINavigationController) -> Bool {
let alert = UIAlertController(title: "Do you want to go back?",
message: "Do you really want to go back? Tap on \"Yes\" to go back. Tap on \"No\" to stay on this screen.",
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: nil))
alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { _ in
navigationController.popViewController(animated: true)
}))
present(alert, animated: true, completion: nil)
return false
}
}
Sie können meine Demo hier ansehen .