ios - extension - swift protocol and delegate example
Swift 2.2#selector en error de compilador de extensión de protocolo (5)
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
Ningún método declarado con Objective-C Selector.
Intenté reproducir el problema en estas pocas líneas de código, que se pueden copiar y pegar fácilmente también en el patio de recreo
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 para agregar a ese método en el protocolo @objc
, pero si lo hago me pide que también lo agregue a la clase que lo implementa, pero una vez que agrego la clase ya no cumple con el protocolo, porque No parece ver la implementación en la extensión del protocolo.
¿Cómo puedo implementar esto correctamente?
Esta respuesta es bastante similar a Bruno Hecktheuers, pero en lugar de que todos los que quieran cumplir con el protocolo "Tappable" implementen 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 donde sea que se use:
addTapGestureRecognizer(selector: #selector(self.tapGestureDetected(_:)))
De esta manera, evitamos que los que implementan este protocolo tengan que implementar la variable selectora y también evitamos tener que marcar a todos los que usen este protocolo con "@objc". Se siente como este enfoque es menos hinchado.
Este es un ejemplo @objc
utiliza Swift 3. Utiliza un protocolo Swift estándar sin la necesidad de ninguna decoración @objc
y una extensión privada para definir la función de devolución de llamada.
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)
}
}
Puede crear una propiedad que es 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 deja de mostrarse y no es más necesario configurar su protocolo y clase con el decorador @objc.
Esta solución no es la más elegante, pero se ve bien hasta ahora.
Sucedió que vi esto en la barra lateral, recientemente tuve este mismo problema ... Desafortunadamente, debido a las limitaciones del tiempo de ejecución de Objective-C, no puede usar @objc en las extensiones de protocolo, creo que este problema se cerró a principios de 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 desde cualquier cosa que subclasifique NSObject y se ajuste al protocolo. Esto se hace más a menudo con la delegación.
Esto implica que podría crear una subclase de contenedor vacío que se ajuste al protocolo y usar el contenedor para llamar a sus métodos desde el protocolo que se define en el contenedor, cualquier otro método indefinido del protocolo puede pasarse al delegado. Existen otras soluciones similares que utilizan una extensión privada de una clase concreta, como UIViewController y definen un método que llama al método de protocolo, pero también están vinculados a una clase particular y no a una implementación predeterminada de una clase particular que cumple con la norma. protocolo
Tenga en cuenta que está intentando 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()
}
Ver:
Utilice un delegado y defina un método de envoltura 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:
Conforme el controlador de vista al delegado de la vista e implemente 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 tuvo que ajustarse al delegado y no configuró el selector de alguna propiedad de la vista, esto separa la vista (y su protocolo) del controlador de vista.
Ya tienes una UIView llamada TapView que se hereda de UIView y Tappable, por lo que tu 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")
}
}
Tuve un problema similar. Aquí esta lo que hice.
- Marcó el protocolo como @objc.
- Marcé todos los métodos que extendí con un comportamiento predeterminado como opcional.
Entonces usé el yo. 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 } }