web-dev-qa-db-fra.com

Swift 2.2 #selector dans une erreur du compilateur d'extension de protocole

J'ai une extension de protocole qui fonctionnait parfaitement avant Swift 2.2.

Maintenant, j'ai un avertissement qui me dit d'utiliser le nouveau #selector, mais si je l'ajoute

aucune méthode déclarée avec Objective-C Selector.

J'ai essayé de reproduire le problème dans ces quelques lignes de code, qui peuvent être facilement copiées et collées également dans un terrain de jeu.

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

Il est également suggéré d’ajouter à cette méthode dans le protocole @objc, mais si je le fais, il me demande également de l’ajouter à la classe qui l’implémente. ne semble pas voir la mise en œuvre dans l'extension de protocole .
Comment puis-je implémenter ceci correctement?

25
Andrea

J'avais un problème similaire. Voici ce que j'ai fait.

  1. A marqué le protocole comme @objc.
  2. A marqué les méthodes que j'ai étendues avec un comportement par défaut comme facultatif.
  3. Alors utilisé Self. dans le sélecteur.

    @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
someoneAnyone

Vous pouvez créer une propriété qui est un sélecteur ... Exemple:

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'erreur cesse de s'afficher et il n'est plus nécessaire de définir votre protocole et votre classe avec le décorateur @objc.

Cette solution n’est pas la plus élégante, mais a l’air OK jusqu’à présent.

16
Bruno Hecktheuer

Cette réponse est assez similaire à celle de Bruno Hecktheuers, mais au lieu d’avoir toutes celles qui souhaitent se conformer au protocole "Tappable" qui implémente la variable "sélecteur", nous avons choisi de la transmettre en tant que paramètre à la fonction 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")
    }
}

puis passez le sélecteur partout où il est utilisé:

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

De cette façon, nous évitons que ceux qui implémentent ce protocole aient à implémenter la variable de sélecteur et nous évitons également de devoir marquer tout le monde utilisant ce protocole avec "@objc". On se sent comme si cette approche est moins gonflée.

7
Peep

Voici un exemple de travail utilisant Swift 3. Il utilise un protocole Swift standard sans avoir besoin de décorations @objc ni d’une extension privée pour définir la fonction de rappel.

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
picciano

Il m'est arrivé de voir cela dans la barre latérale. J'ai récemment eu le même problème. Malheureusement, en raison des limitations d'exécution d'Objective-C, vous ne pouvez pas utiliser @objc pour les extensions de protocole, je pense que ce problème a été fermé au début de cette année.

Le problème se pose car l'extension est ajoutée après la conformité du protocole. Il n'y a donc aucun moyen de garantir que la conformité au protocole est respectée. Cela dit, il est possible d'appeler une méthode en tant que sélecteur à partir de tout ce qui sous-classe NSObject et est conforme au protocole. Cela se fait le plus souvent avec délégation. 

Cela implique que vous puissiez créer une sous-classe de wrapper vide conforme au protocole et utiliser le wrapper pour appeler ses méthodes à partir du protocole défini dans le wrapper. Toute autre méthode non définie du protocole peut être transmise au délégué. Il existe d’autres solutions similaires qui utilisent une extension privée d’une classe concrète, telle que UIViewController, et définissent une méthode qui appelle la méthode du protocole, mais elles sont également liées à une classe particulière et non à une implémentation par défaut d’une classe particulière qui se produit). conforme au protocole.

Réalisez que vous essayez d'implémenter une implémentation par défaut d'une fonction de protocole qui utilise une autre de ses propres fonctions de protocole pour définir une valeur pour sa propre implémentation. ouf! 

Protocole:

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

Vue:

Utilisez un délégué et définissez une méthode wrapper pour décompresser en toute sécurité la méthode du délégué.

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:

Conformez le contrôleur de vue au délégué de la vue: et implémentez la méthode du protocole.

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 

   }
}

Notez que le contrôleur de vue devait seulement se conformer au délégué et ne définissait pas le sélecteur de certaines propriétés de la vue, cela séparait la vue (et son protocole) du contrôleur de vue.

Vous avez déjà un UIView nommé TapView qui hérite de UIView et de Tappable afin que votre implémentation puisse être:

Protocole:

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
RLoniello