Może jest już trochę za późno, ale chciałem też wcześniej takiego zachowania. Rozwiązanie, które wybrałem, działa całkiem dobrze w jednej z aplikacji obecnie dostępnych w App Store. Ponieważ nie widziałem nikogo, kto stosuje podobną metodę, chciałbym się nią tutaj podzielić. Wadą tego rozwiązania jest to, że wymaga podklasy UINavigationController. Chociaż użycie Method Swizzling może pomóc w uniknięciu tego, nie posunąłem się tak daleko.
Tak więc domyślny przycisk Wstecz jest faktycznie zarządzany przez UINavigationBar. Gdy użytkownik naciśnie przycisk Wstecz, UINavigationBarzapytaj delegata, czy powinien otworzyć górną część UINavigationItem, dzwoniąc navigationBar(_:shouldPop:). UINavigationControllerfaktycznie implementuje to, ale nie deklaruje publicznie, że przyjmuje UINavigationBarDelegate(dlaczego !?). Aby przechwycić to zdarzenie, utwórz podklasę UINavigationController, zadeklaruj jej zgodność UINavigationBarDelegatei zaimplementuj navigationBar(_:shouldPop:). Zwróć, truejeśli górny element powinien zostać usunięty. Wróć, falsejeśli powinno zostać.
Są dwa problemy. Po pierwsze, musisz kiedyś wywołać UINavigationControllerwersję programu navigationBar(_:shouldPop:). Ale UINavigationBarControllernie deklaruje publicznie, że jest zgodny z UINavigationBarDelegate, próba wywołania spowoduje błąd czasu kompilacji. Rozwiązaniem, które wybrałem, jest użycie środowiska uruchomieniowego Objective-C, aby bezpośrednio pobrać implementację i wywołać ją. Daj mi znać, jeśli ktoś ma lepsze rozwiązanie.
Innym problemem jest to, że navigationBar(_:shouldPop:)najpierw wywoływana jest następująca popViewController(animated:)po dotknięciu przycisku Wstecz. Kolejność jest odwracana, jeśli kontroler widoku jest otwierany przez wywołanie popViewController(animated:). W tym przypadku używam wartości logicznej, aby wykryć, czy popViewController(animated:)została wywołana przed, navigationBar(_:shouldPop:)co oznacza, że użytkownik nacisnął przycisk Wstecz.
Robię również rozszerzenie, UIViewControlleraby umożliwić kontrolerowi nawigacji pytanie kontrolera widoku, czy należy go otworzyć, jeśli użytkownik naciśnie przycisk Wstecz. Kontrolery widoku mogą wrócić falsei wykonać niezbędne czynności, a popViewController(animated:)później zadzwonić .
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
}
}
A widząc kontrolery, implementuj shouldBePopped(_:). Jeśli nie zaimplementujesz tej metody, domyślnym zachowaniem będzie wyskakiwanie kontrolera widoku, gdy tylko użytkownik naciśnie przycisk Wstecz, tak jak zwykle.
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
}
}
Możesz obejrzeć moje demo tutaj .
