My app starts with a login screen that segues to CreateRequestTableViewController, and everything is embedded in a navigation controller, so the back button for the CreateRequest vc goes back to the login screen. I want to ask the user if they're sure they before they're logged out and the navcon pops the vc to show the Login screen again.
I've gotten it to work with the code below, except that after I log back in and move back to the CreateRequest VC (creating a new instance) I get a fatal error:
'NSInternalInconsistencyException', reason: 'Cannot manually set the delegate on a UINavigationBar managed by a controller.'
This puts me in just a little bit over my head. I've tried adding the deinit method that's included in the code below, with no luck.
It's especially strange that it doesn't crash the first time I assign the delegate (or when I set it to nil either), as the text of the error would suggest.
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.delegate = self
}
deinit {
navigationController?.navigationBar.delegate = nil
}
func confirmLogout() {
let alert = UIAlertController(title: "Log Out", message: "Are you sure you want to log out?", preferredStyle: .alert)
let yesButton = UIAlertAction(title: "Log out", style: .destructive) { (_) in
if let loginVC = self.navigationController?.viewControllers.first as? SignInTableViewController {
self.navigationController?.popViewController(animated: true)
loginVC.logOutAll()
}
}
let noButton = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alert.addAction(yesButton)
alert.addAction(noButton)
present(alert, animated: true, completion: nil)
}
func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
if navigationController?.viewControllers.last is CreateRequestTableViewController {
confirmLogout()
return false
}
navigationController?.popViewController(animated: true)
return true
}
I've solved this problem by deriving custom navigation controller that sets up it's own specialised navigation bar delegate.
This delegate (Forwarder):
The custom navigation controller adds a new "navigationBarDelegate" property that you can use to setup your delegate. You should do that in viewDidAppear:animated: and viewWillDisappear:animated: methods.
Here is the code (Swift 4):
class NavigationController : UINavigationController
{
fileprivate var originaBarDelegate:UINavigationBarDelegate?
private var forwarder:Forwarder? = nil
var navigationBarDelegate:UINavigationBarDelegate?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if navigationBar.delegate != nil {
forwarder = Forwarder(self)
}
}
}
fileprivate class Forwarder : NSObject, UINavigationBarDelegate {
weak var controller:NavigationController?
init(_ controller: NavigationController) {
self.controller = controller
super.init()
controller.originaBarDelegate = controller.navigationBar.delegate
controller.navigationBar.delegate = self
}
let shouldPopSel = #selector(UINavigationBarDelegate.navigationBar(_:shouldPop:))
let didPopSel = #selector(UINavigationBarDelegate.navigationBar(_:didPop:))
let shouldPushSel = #selector(UINavigationBarDelegate.navigationBar(_:shouldPush:))
let didPushSel = #selector(UINavigationBarDelegate.navigationBar(_:didPush:))
func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
if let delegate = controller?.navigationBarDelegate, delegate.responds(to: shouldPopSel) {
if !delegate.navigationBar!(navigationBar, shouldPop: item) {
return false
}
}
if let delegate = controller?.originaBarDelegate, delegate.responds(to: shouldPopSel) {
return delegate.navigationBar!(navigationBar, shouldPop: item)
}
return true
}
func navigationBar(_ navigationBar: UINavigationBar, didPop item: UINavigationItem) {
if let delegate = controller?.navigationBarDelegate, delegate.responds(to: didPopSel) {
delegate.navigationBar!(navigationBar, didPop: item)
}
if let delegate = controller?.originaBarDelegate, delegate.responds(to: didPopSel) {
return delegate.navigationBar!(navigationBar, didPop: item)
}
}
func navigationBar(_ navigationBar: UINavigationBar, shouldPush item: UINavigationItem) -> Bool {
if let delegate = controller?.navigationBarDelegate, delegate.responds(to: shouldPushSel) {
if !delegate.navigationBar!(navigationBar, shouldPush: item) {
return false
}
}
if let delegate = controller?.originaBarDelegate, delegate.responds(to: shouldPushSel) {
return delegate.navigationBar!(navigationBar, shouldPush: item)
}
return true
}
func navigationBar(_ navigationBar: UINavigationBar, didPush item: UINavigationItem) {
if let delegate = controller?.navigationBarDelegate, delegate.responds(to: didPushSel) {
delegate.navigationBar!(navigationBar, didPush: item)
}
if let delegate = controller?.originaBarDelegate, delegate.responds(to: didPushSel) {
return delegate.navigationBar!(navigationBar, didPush: item)
}
}
}
Here is the usage:
Put the following code into your view controller
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
(navigationController as? NavigationController)?.navigationBarDelegate = self
}
override func viewWillDisappear(_ animated: Bool) {
(navigationController as? NavigationController)?.navigationBarDelegate = nil
super.viewWillDisappear(animated)
}
implement one or more of UINavigationBarDelegate methods in your view controller (this is just an example):
func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
let alert = UIAlertController(title: "Do you really want to leave the page?", message: "All changes will be lost", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Stay here", style: .default, handler: nil))
alert.addAction(UIAlertAction(title: "Leave", style: .destructive, handler: { action in
self.navigationController?.popViewController(animated: true)
}))
self.present(alert, animated: true)
return false
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With