Swift 2.2 # selector en el error del compilador de extensión de protocolo


Tengo una extensión de protocolo que solía funcionar perfectamente antes de swift 2.2.

Ahora tengo una advertencia que me dice que use el nuevo #selector, pero si lo agrego

No se ha declarado ningún método con el Selector Objective-C.

Intenté reproducir el problema en estas pocas líneas de código, que se pueden copiar y pegar fácilmente también en playground

  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")
    }
}

También hay una sugerencia de anexar a ese método en el protocolo @objc, pero si lo hago me pide también que agregarlo a la clase que lo implementa, pero una vez que agrego la clase ya no se ajusta al protocolo, porque no parece ver la implementación en la extensión del protocolo.
¿Cómo puedo implementar esto correctamente?

Author: Andrea, 2016-03-23

5 answers

Tuve un problema similar. esto es lo que hice.

  1. Marcó el protocolo como @objc.
  2. Marcó cualquier método que extendí con un comportamiento predeterminado como opcional.
  3. Luego usé Yo Mismo. en el #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
      }
    }
    
 24
Author: someoneAnyone,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-19 04:49:50

Puede crear una propiedad que sea un Selector... Ejemplo:

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")
    }
}

El error se detiene y ya no es necesario establecer su protocolo y clase con el decorador @objc.

Esta solución no es la más elegante, pero se ve bien hasta ahora.

 15
Author: Bruno Hecktheuer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-15 20:51:06

Esta respuesta es bastante similar a Bruno Hecktheuers, pero en lugar de tener a todos los que quieran ajustarse al protocolo "Tappable" implementar la variable "selector", elegimos pasarla como parámetro a la función 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")
    }
}

Y luego simplemente pase el selector dondequiera que se use:

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

De esta manera evitamos que los que implementan este protocolo tengan que implementar la variable selector y también evitamos tener que marcar a todos los que usan esto protocolo con "@objc". Parece que este enfoque es menos hinchado.

 6
Author: Peep,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-16 12:54:30

Aquí hay un ejemplo de trabajo usando Swift 3. Utiliza un protocolo Swift estándar sin necesidad de decoraciones @objc y una extensión privada para definir la función callback.

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)
    }

}
 4
Author: picciano,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-14 20:16:56

Sucedió que vi esto en la barra lateral, recientemente tuve este mismo problema.. Desafortunadamente, debido a las limitaciones de tiempo de ejecución de Objective-C, no puede usar @objc en extensiones de protocolo, creo que este problema se cerró a principios de este año.

El problema surge porque la extensión se agrega después de la conformidad del protocolo, por lo que no hay forma de garantizar que se cumpla la conformidad con el protocolo. Dicho esto, es posible llamar a un método como un selector de cualquier cosa que subclases NSObject y se ajusta al protocolo. Esto se hace con mayor frecuencia con delegación.

Esto implica que podría crear una subclase de envoltura vacía que se ajuste al protocolo y usar la envoltura para llamar a sus métodos desde el protocolo que se definen en la envoltura, cualquier otro método indefinido del protocolo se puede pasar al delegado. Hay otras soluciones similares que usan una extensión privada de una clase concreta como UIViewController y definen un método que llama el método protocol, pero estos también están vinculados a una clase en particular y no una implementación predeterminada de una clase en particular que se ajusta al protocolo.

Tenga en cuenta que está tratando de implementar una implementación predeterminada de una función de protocolo que utiliza otra de sus propias funciones de protocolo para definir un valor para su propia implementación. ¡uf!

Protocolo:

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

Vista:

Usa un delegado y define un wrapper método para desenvolver de forma segura el método del delegado.

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:

Conformar el Controlador de vista al delegado de la vista: e implementar el método del protocolo.

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's Method
    func update() {
        print("Delegating work from View that Conforms to CustomViewDelegate to View Controller")
    }

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

       //Do something else 

   }
}

Tenga en cuenta que el controlador de vista solo tenía que ajustarse al delegado y no estableció el selector de alguna propiedad de la vista, esto separa la vista (y su protocolo) del controlador de vista.

Ya tienes una UIView nombre TapView que hereda de UIView y Tocar así su implementación podría ser:

Protocolo:

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")
   }

}
 0
Author: RLoniello,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-18 12:09:14