2016-03-23 28 views
23

Ho un'estensione del protocollo che funzionava perfettamente prima di swift 2.2.
Swift 2.2 #selector nell'errore del compilatore dell'estensione del protocollo

ora ho un avvertimento che mi dice di utilizzare il nuovo #selector, ma se lo aggiungo

nessun metodo dichiarato con selettore Objective-C.

ho cercato di riprodurre il problema in queste poche righe di codice, che può essere facilmente copiare e incollare anche nel parco giochi

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

C'è anche un suggerimento da aggiungere a tale metodo nel protocollo @objc, ma se lo faccio mi chiede anche di aggiungerlo alla classe che lo implementa, ma una volta che aggiungo la classe non è più conforme al protocollo, perché non sembra vedere l'implementazione nell'estensione del protocollo.
Come posso implementarlo correttamente?

+0

Potresti pubblicare un esempio compilabile? Come viene dichiarato 'panGestureDetected'? – Sulthan

+0

L'aggiungerò domani, grazie Sulthan – Andrea

+0

@Andrea Cambia Pannable.panGestureDetected (_ :). metti il ​​nome della classe dove viene dichiarato panGestureDetected invece di 'Pannable.whatever' metti 'YourClass.panGestureDetected (_ :) ' –

risposta

20

Ho avuto un problema simile. ecco quello che ho fatto.

  1. Contrassegnato come @objc.
  2. Contrassegnato tutti i metodi che ho esteso con un comportamento predefinito come facoltativo.
  3. Quindi auto usato. in #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 
        } 
    } 
    
+0

Sì, funziona! Grazie – Andrea

+0

@Andrea, Cool! Sono contento di poterti aiutare. – someoneAnyone

+0

Ottengo un "Uso di identificatore non risolto" Self. Il mio protocollo non è contrassegnato come pubblico –

15

È possibile creare una proprietà che è un selettore ... Esempio:

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

L'errore si ferma a mostrare e non è più necessario impostare il protocollo e la classe con la @objc decoratore.

Questa soluzione non è la più elegante, ma sembra ok fino ad ora.

+0

Sì, non è fantastico ma molto meglio che mettere @objc ovunque, in particolare se si "eredita" da una catena di altri protocolli con le loro implementazioni predefinite. cambiare l'intero codice base solo a causa di quel selettore. –

+0

come chiamare la propria funzione di protocollo come selettore, lo faccio non voglio dichiarare il selettore in classe estendendo il protocollo ... –

6

Questa risposta è abbastanza simile a Bruno Hecktheuers, ma invece di avere tutti coloro che vogliono essere conformi al protocollo "tappable" attuare il "selettore" variabile, abbiamo scelto di passarla come parametro alla funzione 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") 
    } 
} 

e poi semplicemente passare il selettore ovunque venga utilizzato:

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

In questo modo si evita di dover quelli di attuazione del presente protocollo dover attuare la variabile selettore ed inoltre evitare di dover marcare everyon e usando questo protocollo con "@objc". Sembra che questo approccio sia meno gonfiato.

+0

grazie a @Peep nice approach – Andrea

+4

E se si desidera fornire un'implementazione predefinita di 'tapGestureDetected' nell'estensione del protocollo stessa, non nella classe concreta che implementa il protocollo? – NRitH

3

Ecco un esempio di utilizzo di Swift 3. Utilizza un protocollo Swift standard senza la necessità di decorazioni @objc e di un'estensione privata per definire la funzione di 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) 
    } 

} 
0

Mi è capitato di vedere questo nella barra laterale, ho avuto di recente questo stesso problema ..Sfortunatamente, a causa delle limitazioni del runtime Objective-C non è possibile utilizzare @objc sulle estensioni del protocollo, credo che questo problema sia stato chiuso all'inizio di quest'anno.

Il problema sorge perché l'estensione viene aggiunta dopo la conformità del protocollo, quindi non c'è modo di garantire che la conformità al protocollo sia soddisfatta. Detto questo, è possibile chiamare un metodo come selettore da qualsiasi sottoclasse di NSObject e conforme al protocollo. Questo è più spesso fatto con delega.

Ciò implica che è possibile creare una sottoclasse wrapper vuota conforme al protocollo e utilizzare il wrapper per richiamare i suoi metodi dal protocollo definito nel wrapper, eventuali altri metodi non definiti dal protocollo possono essere passati al delegato. Esistono altre soluzioni simili che utilizzano un'estensione privata di una classe concreta come UIViewController e definiscono un metodo che chiama il metodo del protocollo, ma sono anche legate a una particolare classe e non a un'implementazione predefinita di una particolare classe che si conforma a il protocollo.

Realizzare che si sta tentando di implementare un'implementazione predefinita di una funzione di protocollo che utilizza un'altra delle proprie funzioni di protocollo per definire un valore per la propria implementazione. wow!

Protocollo:

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

Vista:

Utilizzare un delegato, e definire un metodo wrapper per scartare in modo sicuro il metodo del delegato.

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:

conformare il controller della vista al delegato della vista: e implementare il metodo del protocollo.

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 

    } 
} 

noti che il controller della vista aveva solo conformarsi al delegato e non impostare il selettore di alcune proprietà della vista, questo separa la vista (ed è protocollo) dal controller della vista.

hai già un UIView chiamato toccare Visualizza che eredita da UIView e tappable così l'implementazione potrebbe essere:

Protocollo:

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

}