Добавление Target-Action в расширение протокола завершается неудачно
У меня есть набор контроллеров представления, которые будут иметь кнопку строки меню. Я создал протокол для этих 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, которые соответствуют протоколу.
Что-то я не так делаю здесь? Заранее спасибо.
4 ответа
Это старый вопрос, но я столкнулся с той же проблемой и нашел решение, которое может быть не идеальным, но это единственный способ, которым я мог придумать.
По-видимому, даже в Swift 3 невозможно установить целевое действие для вашего расширения протокола. Но вы можете достичь желаемой функциональности без реализации вашего func menuTapped()
Метод во всех ваших ViewControllers, который соответствует вашему протоколу.
сначала давайте добавим новые методы в ваш протокол
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
должен реализовать этот метод.
В вашем ViewController,
class MapViewController: UIViewController, CenterViewControllerProtocol {
weak var containerDelegate: ContainerViewControllerProtocol?
override func viewDidLoad() {
super.viewDidLoad()
setupMenuBarButton()
}
func menuTappedInVC()
{
self.menuTapped()
}
Все, что вам нужно сделать, это реализовать menuTappedInVC()
метод в вашем VC, и это будет ваш метод целевого действия. В рамках этого вы можете делегировать эту задачу обратноmenuTapped
который уже реализован в вашем расширении протокола.
Это компилируется, но происходит сбой также в бета-версии XCode7.3, поэтому, наконец, вы должны использовать уродливый суперкласс в качестве цели действия, и я полагаю, что это то, чего вы и я пытаемся избежать.
Кажется, что использование расширений протокола не поддерживается в данный момент. Согласно ответу Fluidsonic здесь:
В любом случае все функции, которые вы собираетесь использовать с помощью селектора, должны быть помечены динамическими или @objc. Если это приводит к ошибке, что @objc нельзя использовать в этом контексте, то то, что вы пытаетесь сделать, просто не поддерживается ".
В вашем примере, я думаю, что одним из способов решения этой проблемы было бы создание подкласса UIBarButtonItem, который вызывает блок при каждом его нажатии. Тогда вы могли бы позвонить containerDelegate?.toggleSideMenu()
внутри этого блока.
Я думаю, что вы можете обернуть 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);
}
}
}