例外:无法在控制器管理的 UINavigationBar 上手动设置委托 [英] Exception: Cannot manually set the delegate on a UINavigationBar managed by a controller

查看:12
本文介绍了例外:无法在控制器管理的 UINavigationBar 上手动设置委托的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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.

我已经让它与下面的代码一起工作,除了在我重新登录并移回 CreateRequest VC(创建一个新实例)后,我收到一个致命错误:

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: '无法在控制器管理的 UINavigationBar 上手动设置委托.'

这让我有点过头了.我尝试添加下面代码中包含的 deinit 方法,但没有成功.

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.

特别奇怪的是,它在我分配委托时(或者当我将其设置为 nil 时)第一次没有崩溃,正如错误文本所暗示的那样.

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.

此委托(转发者):

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

自定义导航控制器添加了一个新的navigationBarDelegate"属性,您可以使用它来设置您的委托.你应该在 viewDidAppear:animated: 和 viewWillDisappear:animated: 方法中这样做.

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.

这里是代码(Swift 4):

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)
        }
    }
}

用法如下:

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

  • 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)
}

  • 在您的视图控制器中实现一个或多个 UINavigationBarDelegate 方法(这只是一个示例):

  • 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
    }
    

  • 这篇关于例外:无法在控制器管理的 UINavigationBar 上手动设置委托的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆