Может быть, уже слишком поздно, но я и раньше хотел такого же поведения. И решение, которое я выбрал, довольно хорошо работает в одном из приложений, которые сейчас есть в 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
}
}
Вы можете посмотреть мою демонстрацию здесь .