例外:无法在由控制器管理的UINavigationBar上手动设置委托

我的应用程序以一个登录到CreateRequestTableViewController的登录屏幕开始,一切都嵌入在导航控制器中,因此CreateRequest vc的后退按钮返回到登录屏幕。 我想询问用户他们在注销之前是否确定他们并且navcon弹出vc以再次显示登录屏幕。

我已经使用它来处理下面的代码,除了在我重新登录并返回到CreateRequest VC(创建一个新实例)后,我得到一个致命的错误:

'NSInternalInconsistencyException', reason: 'Cannot manually set the delegate on a UINavigationBar managed by a controller.'

这让我有点过头了。 我已经尝试添加下面代码中包含的deinit方法,没有运气。

特别奇怪的是,它在一次分配委托时没有崩溃(或者当我将其设置为nil时),因为错误的文本会提示。

 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 } 

我通过派生自定义导航控制器来设置它自己的专用导航栏代表,从而解决了这个问题。

这个代表(货代):

  • 仅在导航控制器控制导航栏(在导航控制器的初始化程序中)之前设置一次。
  • 接收UINavigationBarDelegate消息并尝试首先调用导航栏委托,然后最终调用原始导航栏委托(UINavigationController)。

自定义导航控制器添加了一个新的“navigationBarDelegate”属性,您可以使用该属性来设置您的委托。 你应该在viewDidAppear:animated:和viewWillDisappear:animated:methods中做到这一点。

这是代码(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) } } } 

这是用法:

  • 从UINavigationBarDelegate派生您的视图控制器
  • 将故事板中导航控制器的类名从UINavigationController更改为NavigationController。
  • 将以下代码放入视图控制器中

     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) } 
  • 在视图控制器中实现一个或多个UINavigationBarDelegate方法(这只是一个例子):

     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 } 

错误消息很明确。 您可以自由设置导航控制器的委托。 但是你不能设置其导航栏的代理 ; 如果你这样做,你将打破导航控制器,因为是栏的代表。