【问题标题】:Adding Target-Action in Protocol Extension fails在协议扩展中添加 Target-Action 失败
【发布时间】:2015-11-25 14:23:32
【问题描述】:

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

我的协议看起来像,

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 中实现该方法,它就可以正常工作。但是我会在所有符合协议的 viewController 中复制代码。

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

【问题讨论】:

    标签: ios swift protocol-extension


    【解决方案1】:

    目前似乎不支持使用协议扩展。根据fluidsonic's回答here

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

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

        【解决方案3】:

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

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

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

        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”。而每一个符合CenterViewControllerProtocol的ViewController都必须实现这个方法。

        在您的 ViewController 中,

        class MapViewController: UIViewController, CenterViewControllerProtocol {
        
            weak var containerDelegate: ContainerViewControllerProtocol?
        
            override func viewDidLoad() {
                super.viewDidLoad()
        
                setupMenuBarButton()
            }
        
            func menuTappedInVC()
            {
              self.menuTapped()
            }
        

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

        【讨论】:

          【解决方案4】:

          我认为您可以包装 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);
                  }
              }
          
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多