12

我有一组视图控制器,它们将有一个菜单栏按钮。我为这些视图控制器创建了一个协议来采用。此外,我还扩展了协议以添加默认功能。

我的协议看起来像,

protocol CenterViewControllerProtocol: class {

    var containerDelegate: ContainerViewControllerProtocol? { get set }

    func setupMenuBarButton()
}

而且,扩展看起来像这样,

extension CenterViewControllerProtocol where Self: UIViewController {

    func setupMenuBarButton() {
        let barButton = UIBarButtonItem(title: "Menu", style: .Done, target: self, action: "menuTapped")
        navigationItem.leftBarButtonItem = barButton
    }

    func menuTapped() {
        containerDelegate?.toggleSideMenu()
    }
}

我的 viewController 采用了协议——

class MapViewController: UIViewController, CenterViewControllerProtocol {

    weak var containerDelegate: ContainerViewControllerProtocol?

    override func viewDidLoad() {
        super.viewDidLoad()

        setupMenuBarButton()
    }
}

我的按钮显示得很好,但是当我点击它时,应用程序崩溃了

[AppName.MapViewController menuTapped]: unrecognized selector sent to instance 0x7fb8fb6ae650

如果我在 ViewController 中实现该方法,它可以正常工作。但是我会在所有符合协议的视图控制器中复制代码。

我在这里做错什么了吗?提前致谢。

4

4 回答 4

0

这在 Xcode7.3 Beta 中编译但也会崩溃,所以最后你应该使用一个丑陋的超类作为动作的目标,我想这是你和我试图避免的。

于 2016-02-01T16:43:13.030 回答
0

这是一个老问题,但我也遇到了同样的问题,并提出了一个可能不完美的解决方案,但这是我能想到的唯一方法。

显然,即使在 Swift 3 中,也无法为您的协议扩展设置目标操作。但是您可以实现所需的功能,而无需func menuTapped()在所有符合您协议的 ViewController 中实现您的方法。

首先让我们为您的协议添加新方法

protocol CenterViewControllerProtocol: class {

    var containerDelegate: ContainerViewControllerProtocol? { get set }

    //implemented in extension
    func setupMenuBarButton()
    func menuTapped()

    //must implement in your VC
    func menuTappedInVC()
}

现在像这样改变你的扩展

extension CenterViewControllerProtocol where Self: UIViewController {

        func setupMenuBarButton() {
            let barButton = UIBarButtonItem(title: "Menu", style: .Done, target: self, action: "menuTappedInVC")
            navigationItem.leftBarButtonItem = barButton
        }

        func menuTapped() {
            containerDelegate?.toggleSideMenu()
        }
    }

注意现在按钮的动作在你的扩展中是 "menuTappedInVC" ,而不是 "menuTapped" 。并且每一个符合的 ViewController 都CenterViewControllerProtocol必须实现这个方法。

在您的视图控制器中,

class MapViewController: UIViewController, CenterViewControllerProtocol {

    weak var containerDelegate: ContainerViewControllerProtocol?

    override func viewDidLoad() {
        super.viewDidLoad()

        setupMenuBarButton()
    }

    func menuTappedInVC()
    {
      self.menuTapped()
    }

您所要做的就是menuTappedInVC()在您的 VC 中实现方法,这将是您的目标操作方法。在其中,您可以将该任务委托给menuTapped您的协议扩展中已经实现的任务。

于 2017-03-14T18:57:17.110 回答
0

我认为您可以包装 Target-Action 以从它们中创建 Closure,然后以与我使用 Target-Action 的 UIGestureRecognizer 类似的方式使用它

protocol SomeProtocol { 
    func addTouchDetection(for view: UIView)
}

extension SomeProtocol {

    func addTouchDetection(for view: UIView) {

        let tapGestureRecognizer = UITapGestureRecognizer(callback: { recognizer in

           // recognizer.view
        })
        view.addGestureRecognizer(tapGestureRecognizer)
    }
}

// MARK: - IMPORTAN EXTENSION TO ENABLE HANDLING GESTURE RECOGNIZER TARGET-ACTIONS AS CALLBACKS
extension UIGestureRecognizer {

    public convenience init(callback: @escaping (_ recognizer: UIGestureRecognizer) -> ()) {
        let wrapper = CallbackWrapper(callback)
        self.init(target: wrapper, action: #selector(CallbackWrapper.callCallback(_:)))

        // retaint callback wrapper
        let key = UnsafeMutablePointer<Int8>.allocate(capacity: 1);
        objc_setAssociatedObject(self, key, wrapper, .OBJC_ASSOCIATION_RETAIN)
    }

    class CallbackWrapper {
        var callback : (_ recognizer: UIGestureRecognizer) -> ();
        init(_ callback: @escaping (_ recognizer: UIGestureRecognizer) -> ()) {
            self.callback = callback;
        }
        @objc public func callCallback(_ recognizer: UIGestureRecognizer) {
            self.callback(recognizer);
        }
    }

}
于 2018-09-21T11:09:43.657 回答
0

目前似乎不支持使用协议扩展。根据fluidsonic在这里的回答:

在任何情况下,您打算通过选择器使用的所有功能都应该用动态或@objc 标记。如果这导致无法在此上下文中使用 @objc 的错误,那么您尝试执行的操作根本不受支持。”

在您的示例中,我认为解决此问题的一种方法是创建 UIBarButtonItem 的子类,该子类在被点击时调用一个块。然后你可以containerDelegate?.toggleSideMenu()在那个块内调用。

于 2015-11-26T05:16:38.327 回答