I've solved this problem by deriving custom navigation controller that sets up it's own specialised navigation bar delegate.
This delegate (Forwarder):
- Is set up only once and before the navigation controller takes
control over the navigation bar (in the navigation controller's
initialiser).
- Receives UINavigationBarDelegate messages and tries to
call your navigation bar delegate first, then eventually the original navigation
bar delegate (the UINavigationController).
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:
- Derive your view controller from UINavigationBarDelegate
- Change the class name of the navigation controller in your storyboard from UINavigationController to NavigationController.
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
}