我有一组视图控制器,它们有一个菜单栏按钮。我为那些viewControllers创建了一个协议来采用。此外,我已经扩展了协议以添加默认功能。
我的协议看起来像,
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中实现该方法,它可以正常工作。但我要复制符合协议的所有viewControllers中的代码。
我在这里做错了什么? 提前谢谢。
答案 0 :(得分:0)
此时似乎不支持使用协议扩展。根据{{3}}回答fluidsonic's:
在任何情况下,您打算通过选择器使用的所有功能都应标记为dynamic或@objc。如果这导致在此上下文中无法使用@objc的错误,则根本不支持您尝试执行的操作。"
在你的例子中,我认为解决这个问题的一种方法是创建一个UIBarButtonItem的子类,只要点击它就会调用一个块。然后你可以在那个区块内调用containerDelegate?.toggleSideMenu()
。
答案 1 :(得分:0)
这也会在Xcode7.3 Beta中编译崩溃,所以最后你应该使用一个丑陋的超类作为动作的目标,我想这是你和我想要避免的。
答案 2 :(得分:0)
这是一个古老的问题,但我也遇到了同样的问题,并提出了一个可能不完美的解决方案,但这是我能想到的唯一方法。
显然即使在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
。
答案 3 :(得分:0)
我认为您可以包装Target-Action以使其封闭,然后以与我对UIGestureRecognizer使用Target-Action相似的方式使用它
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);
}
}
}