Mungkin agak terlambat, tapi saya juga menginginkan perilaku yang sama sebelumnya. Dan solusi yang saya gunakan berfungsi dengan cukup baik di salah satu aplikasi yang saat ini ada di App Store. Karena saya belum melihat ada orang yang menggunakan metode serupa, saya ingin membagikannya di sini. Kelemahan dari solusi ini adalah membutuhkan subclassing UINavigationController
. Meskipun menggunakan Metode Swizzling dapat membantu menghindari itu, saya tidak melangkah sejauh itu.
Jadi, tombol kembali default sebenarnya dikelola oleh UINavigationBar
. Saat pengguna mengetuk tombol kembali, UINavigationBar
tanyakan delegasinya apakah harus memunculkan bagian atas UINavigationItem
dengan menelepon navigationBar(_:shouldPop:)
. UINavigationController
sebenarnya menerapkan ini, tetapi tidak secara terbuka menyatakan bahwa ia mengadopsi UINavigationBarDelegate
(mengapa !?). Untuk menghentikan acara ini, buat subkelas dari UINavigationController
, nyatakan kesesuaiannya, UINavigationBarDelegate
dan implementasikan navigationBar(_:shouldPop:)
. Kembalikan true
jika item teratas harus muncul. Kembalikan false
jika harus tinggal.
Ada dua masalah. Yang pertama adalah Anda harus memanggil UINavigationController
versi navigationBar(_:shouldPop:)
di beberapa titik. Tetapi UINavigationBarController
tidak secara terbuka menyatakan kesesuaiannya UINavigationBarDelegate
, mencoba memanggilnya akan menghasilkan kesalahan waktu kompilasi. Solusi yang saya gunakan adalah dengan menggunakan runtime Objective-C untuk mendapatkan implementasi secara langsung dan memanggilnya. Tolong beritahu saya jika ada yang punya solusi yang lebih baik.
Masalah lainnya adalah yang navigationBar(_:shouldPop:)
dipanggil pertama diikuti oleh popViewController(animated:)
jika pengguna mengetuk tombol kembali. Urutan berbalik jika pengontrol tampilan dimunculkan dengan memanggil popViewController(animated:)
. Dalam hal ini, saya menggunakan boolean untuk mendeteksi jika popViewController(animated:)
dipanggil sebelumnya navigationBar(_:shouldPop:)
yang berarti pengguna telah mengetuk tombol kembali.
Juga, saya membuat perpanjangan UIViewController
untuk membiarkan pengontrol navigasi menanyakan pengontrol tampilan apakah itu harus muncul jika pengguna mengetuk tombol kembali. Pengontrol tampilan dapat kembali false
dan melakukan tindakan yang diperlukan dan menelepon popViewController(animated:)
nanti.
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
}
}
Dan dalam Anda melihat pengontrol, implementasikan shouldBePopped(_:)
. Jika Anda tidak menerapkan metode ini, perilaku default-nya adalah memunculkan pengontrol tampilan segera setelah pengguna mengetuk tombol kembali seperti biasa.
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
}
}
Anda dapat melihat demo saya di sini .