Talvez seja um pouco tarde, mas eu também queria o mesmo comportamento antes. E a solução que escolhi funciona muito bem em um dos aplicativos atualmente na App Store. Como não vi ninguém usar um método semelhante, gostaria de compartilhá-lo aqui. A desvantagem dessa solução é que ela requer subclasses UINavigationController
. Embora usar o Método Swizzling possa ajudar a evitar isso, não fui tão longe.
Portanto, o botão Voltar padrão é gerenciado por UINavigationBar
. Quando um usuário toca no botão Voltar, UINavigationBar
pergunte a seu delegado se ele deve abrir a tampa UINavigationItem
chamando navigationBar(_:shouldPop:)
. UINavigationController
realmente implementa isso, mas não declara publicamente que adota UINavigationBarDelegate
(por quê !?). Para interceptar este evento, crie uma subclasse de UINavigationController
, declare sua conformidade UINavigationBarDelegate
e implemente navigationBar(_:shouldPop:)
. Retorne true
se o item de cima deve ser estourado. Retorne false
se for para ficar.
Existem dois problemas. A primeira é que você deve chamar a UINavigationController
versão de navigationBar(_:shouldPop:)
em algum momento. Mas UINavigationBarController
não declara publicamente que está em conformidade com UINavigationBarDelegate
, tentar chamá-lo resultará em um erro de tempo de compilação. A solução que escolhi é usar o tempo de execução Objective-C para obter a implementação diretamente e chamá-la. Por favor, me avise se alguém tiver uma solução melhor.
O outro problema é que navigationBar(_:shouldPop:)
é chamado primeiro segue por popViewController(animated:)
se o usuário tocar no botão Voltar. A ordem é invertida se o controlador de visualização for acionado chamando popViewController(animated:)
. Neste caso, utilizo um booleano para detectar se popViewController(animated:)
é chamado antes, o navigationBar(_:shouldPop:)
que significa que o usuário pressionou o botão Voltar.
Além disso, faço uma extensão de UIViewController
para permitir que o controlador de navegação pergunte ao controlador de visualização se ele deve ser exibido se o usuário tocar no botão Voltar. Os controladores de visualização podem retornar false
e fazer quaisquer ações necessárias e chamar popViewController(animated:)
mais tarde.
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
}
}
E em você visualiza os controladores, implemente shouldBePopped(_:)
. Se você não implementar este método, o comportamento padrão será abrir o controlador de visualização assim que o usuário tocar no botão Voltar, como de costume.
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
}
}
Você pode ver minha demonstração aqui .