Swift 2.2 #selector в ошибке компилятора расширения протокола

У меня есть расширение протокола, которое оно отлично работало перед быстрым 2.2.

Теперь у меня есть предупреждение, которое говорит мне использовать новый #selector, но если я добавлю его

нет метода, объявленного с помощью Objective-C Selector.

Я попытался воспроизвести проблему в этих нескольких строках кода, которые можно легко скопировать и вставить также в игровую площадку

  protocol Tappable {
    func addTapGestureRecognizer()
    func tapGestureDetected(gesture:UITapGestureRecognizer)
}

extension Tappable where Self: UIView {
    func addTapGestureRecognizer() {
        let gesture = UITapGestureRecognizer(target: self, action:#selector(Tappable.tapGestureDetected(_:)))
        addGestureRecognizer(gesture)
    }
}

class TapView: UIView, Tappable {
    func tapGestureDetected(gesture:UITapGestureRecognizer) {
        print("Tapped")
    }
}

Существует также предложение добавить этот метод в протокол @objc, но если я это сделаю, он также попросит добавить его в класс, который его реализует, но как только я добавлю класс, он не соответствует протокол, поскольку он, похоже, не видит реализации в расширении протокола.
Как я могу реализовать это правильно?

Ответ 1

У меня была аналогичная проблема. вот что я сделал.

  • Пометили протокол как @objc.
  • Отмечены любые методы, которые я расширил с поведением по умолчанию как необязательный.
  • Затем использовал Self. в поле #selector.

    @objc public protocol UpdatableUserInterfaceType {
      optional func startUpdateUITimer()
      optional var updateInterval: NSTimeInterval { get }
      func updateUI(notif: NSTimer)
    }
    
    public extension UpdatableUserInterfaceType where Self: ViewController {
    
      var updateUITimer: NSTimer {
        return NSTimer.scheduledTimerWithTimeInterval(updateInterval, target: self, selector: #selector(Self.updateUI(_:)), userInfo: nil, repeats: true)
      }
    
      func startUpdateUITimer() {
        print(updateUITimer)
      }
    
      var updateInterval: NSTimeInterval {
        return 60.0
      }
    }
    

Ответ 2

Вы можете создать свойство, которое является селектором... Пример:

protocol Tappable {
    var selector: Selector { get }
    func addTapGestureRecognizer()
}

extension Tappable where Self: UIView {
    func addTapGestureRecognizer() {
        let gesture = UITapGestureRecognizer(target: self, action: selector)
        addGestureRecognizer(gesture)
    }
}

class TapView: UIView, Tappable {
    var selector = #selector(TapView.tapGestureDetected(_:))

    func tapGestureDetected(gesture:UITapGestureRecognizer) {
        print("Tapped")
    }
}

Ошибка перестает отображаться, и нет необходимости устанавливать свой протокол и класс с помощью декоратора @objc.

Это решение не самое элегантное, но выглядит нормально до сих пор.

Ответ 3

Этот ответ очень похож на Bruno Hecktheuers, но вместо того, чтобы всем, кто хочет соответствовать протоколу "Tappable", реализовать переменную "селектор", мы решили передать ее как параметр функции addTapGestureRecognizer:

protocol Tappable {
    func addTapGestureRecognizer(selector selector: Selector)
    func tapGestureDetected(gesture:UITapGestureRecognizer)
}

extension Tappable where Self: UIView {
    func addTapGestureRecognizer(selector selector: Selector)
        let gesture = UITapGestureRecognizer(target: self, action: selector)
        addGestureRecognizer(gesture)
    }
}

class TapView: UIView, Tappable {    
    func tapGestureDetected(gesture:UITapGestureRecognizer) {
        print("Tapped")
    }
}

а затем просто передайте селектор, где он используется:

addTapGestureRecognizer(selector: #selector(self.tapGestureDetected(_:)))

Таким образом, мы избегаем того, чтобы внедряя этот протокол в реализацию селекторной переменной, и мы также избегаем отмечать всех, используя этот протокол, с помощью "@objc". Похоже, этот подход менее раздутый.

Ответ 4

Вот рабочий пример с использованием Swift 3. Он использует стандартный протокол Swift без необходимости каких-либо украшений @objc и частного расширения для определения функции обратного вызова.

protocol PlayButtonPlayable {

    // be sure to call addPlayButtonRecognizer from viewDidLoad or later in the display cycle
    func addPlayButtonRecognizer()
    func handlePlayButton(_ sender: UITapGestureRecognizer)

}

fileprivate extension UIViewController {
    @objc func _handlePlayButton(_ sender: UITapGestureRecognizer) {
        if let playable = self as? PlayButtonPlayable {
            playable.handlePlayButton(sender)
        }
    }
}

fileprivate extension Selector {
    static let playTapped =
        #selector(UIViewController._handlePlayButton(_:))
}

extension PlayButtonPlayable where Self: UIViewController {

    func addPlayButtonRecognizer() {
        let playButtonRecognizer = UITapGestureRecognizer(target: self, action: .playTapped)
        playButtonRecognizer.allowedPressTypes = [ NSNumber(value: UIPressType.playPause.rawValue as Int) ]
        view.addGestureRecognizer(playButtonRecognizer)
    }

}

Ответ 5

Мне довелось увидеть это в боковой панели, у меня недавно была такая же проблема. К сожалению, из-за ограничений времени выполнения Objective-C вы не можете использовать @objc для расширений протокола, я считаю, что этот вопрос был закрыт в начале этого года.

Проблема возникает из-за того, что добавление добавляется после согласования протокола, поэтому нет возможности гарантировать соответствие протокола протоколу. Тем не менее, можно вызвать метод как селектор из всего, что подклассы NSObject и соответствует протоколу. Это чаще всего делается с делегацией.

Это означает, что вы можете создать пустой кластер-подкласс, соответствующий протоколу, и использовать оболочку для вызова своих методов из протокола, которые определены в оболочке, любые другие методы undefined из протокола могут быть переданы делегату, Существуют и другие аналогичные решения, которые используют частное расширение конкретного класса, такого как UIViewController, и определяют метод, который вызывает метод протокола, но они также привязаны к определенному классу, а не к реализации по умолчанию конкретного класса, который, как представляется, соответствует протокол.

Поймите, что вы пытаетесь реализовать стандартную реализацию функции протокола, которая использует другие собственные функции протокола, чтобы определить значение для собственной реализации. гмм!

Протокол:

 public protocol CustomViewDelegate {
     func update()
     func nonDelegatedMethod()
}

Вид:

Используйте делегат и определите метод оболочки, чтобы безопасно развернуть метод делегатов.

class CustomView: UIView {

    let updateButton: UIButton = {
        let button = UIButton(frame: CGRect(origin: CGPoint(x: 50, y: 50), size: CGSize(width: 150, height: 50)))
        button.backgroundColor = UIColor.lightGray
        button.addTarget(self, action: #selector(doDelegateMethod), for: .touchUpInside)
        return button
    }()

    var delegate:CustomViewDelegate?

    required init?(coder aDecoder: NSCoder) {
        fatalError("Pew pew, Aghh!")
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        addSubview(updateButton)
    }

    @objc func doDelegateMethod() {
        if delegate != nil {
           delegate!.update()
        } else {
           print("Gottfried: I wanted to be a brain surgeon, but I had a bad habit of dropping things")
        }
     }


   }

ViewController:

Согласовать контроллер представления с делегатом представлений и реализовать метод протоколов.

class ViewController: UIViewController, CustomViewDelegate {

    let customView = CustomView(frame: CGRect(origin: CGPoint(x: 100, y: 100), size: CGSize(width: 200, height: 200)))

    override func viewDidLoad() {
        super.viewDidLoad()
        customView.backgroundColor = UIColor.red
        customView.delegate = self //if delegate is not set, the app will not crash
        self.view.addSubview(customView)
    }

    // Protocol -> UIView Button Action -> View Controller Method
    func update() {
        print("Delegating work from View that Conforms to CustomViewDelegate to View Controller")
    }

    //Protocol > View Controller Required Implementation
    func nonDelegatedMethod() {

       //Do something else 

   }
}

Обратите внимание, что контроллер представления должен был только соответствовать делегату и не устанавливать селектор некоторого свойства представления, это отделяет представление (и его протокол) от контроллера представления.

У вас уже есть UIView с именем TapView, который наследует UIView и Tappable, поэтому ваша реализация может быть:

Протокол:

protocol TappableViewDelegate {
    func tapGestureDetected(gesture:UITapGestureRecognizer)
}

TappableView:

class TappableView: UIView {

    var delegate:TappableViewDelegate?

    required init?(coder aDecoder: NSCoder) {
        fatalError("Pew pew, Aghh!")
    }

    override init(frame: CGRect) {
        super.init(frame: frame)

        let gesture = UITapGestureRecognizer(target: self, action: #selector(doDelegateMethod(gesture:)))
        addGestureRecognizer(gesture)
    }

    @objc func doDelegateMethod(gesture:UITapGestureRecognizer) {
        if delegate != nil {
            delegate!.tapGestureDetected(gesture: gesture)
        } else {
            print("Gottfried: I wanted to be a brain surgeon, but I had a bad habit of dropping things")
        }
    }

}

ViewController:

class ViewController: UIViewController, TappableViewDelegate {

    let tapView = TappableView(frame: CGRect(origin: CGPoint(x: 100, y: 100), size: CGSize(width: 200, height: 200)))

    override func viewDidLoad() {
        super.viewDidLoad()
        tapView.backgroundColor = UIColor.red
        tapView.delegate = self
        self.view.addSubview(tapView)
    }

    func tapGestureDetected(gesture: UITapGestureRecognizer) {
        print("User did tap")
   }

}