Может быть, уже слишком поздно, но я тоже хотел такое же поведение раньше.И решение, с которым я столкнулся, довольно хорошо работает в одном из приложений, которые в настоящее время находятся в App Store.Поскольку я не видел, чтобы кто-то использовал подобный метод, я хотел бы поделиться им здесь.Недостатком этого решения является то, что оно требует подкласса UINavigationController
.Хотя использование метода Swizzling могло бы помочь избежать этого, я не пошел так далеко.
Итак, кнопка возврата по умолчанию фактически управляется UINavigationBar
.Когда пользователь нажимает кнопку «Назад», UINavigationBar
спрашивает своего делегата, должен ли он выдвинуть верхнюю часть UINavigationItem
, вызывая navigationBar(_:shouldPop:)
.UINavigationController
фактически реализует это, но публично не заявляет, что принимает UINavigationBarDelegate
(почему !?).Чтобы перехватить это событие, создайте подкласс UINavigationController
, объявите его соответствие UINavigationBarDelegate
и внедрите navigationBar(_:shouldPop:)
.Верните true
, если верхний элемент должен быть вытолкнут.Верните false
, если оно должно остаться.
Есть две проблемы.Во-первых, в какой-то момент вы должны вызвать UINavigationController
версию navigationBar(_:shouldPop:)
.Но UINavigationBarController
публично не объявляет о его соответствии UINavigationBarDelegate
, попытка вызвать его приведет к ошибке времени компиляции.Решение, которое я выбрал, состоит в том, чтобы использовать среду выполнения Objective C, чтобы получить реализацию и вызвать ее.Пожалуйста, дайте мне знать, если у кого-то есть лучшее решение.
Другая проблема заключается в том, что navigationBar(_:shouldPop:)
вызывается первым, затем следует popViewController(animated:)
, если пользователь нажимает кнопку возврата.Порядок меняется на обратный, если контроллер представления прерывается с помощью вызова popViewController(animated:)
.В этом случае я использую логическое значение, чтобы определить, вызывается ли popViewController(animated:)
до navigationBar(_:shouldPop:)
, что означает, что пользователь нажал на кнопку возврата.
Кроме того, я делаю расширение UIViewController
, чтобынавигационный контроллер спрашивает контроллер представления, должен ли он быть выдвинут, если пользователь нажимает кнопку возврата.Контроллеры представления могут возвращать false
и выполнять любые необходимые действия, а затем вызывать popViewController(animated:)
.
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
}
}
И в ваших контроллерах представления реализовывать shouldBePopped(_:)
.Если вы не реализуете этот метод, поведение по умолчанию будет включать контроллер представления, как только пользователь нажимает кнопку «Назад», как обычно.
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
}
}
Вы можете посмотреть мою демонстрационную версию здесь .
![enter image description here](https://i.stack.imgur.com/TbIt0.gif)