web-dev-qa-db-fra.com

Puis-je faire en sorte que #selector fasse référence à une fermeture dans Swift?

Je veux faire un argument selector de ma méthode faire référence à une propriété de fermeture, les deux existent dans la même portée. Par exemple,

func backgroundChange() {
    self.view.backgroundColor = UIColor.blackColor()
    self.view.alpha = 0.55

    let backToOriginalBackground = {
        self.view.backgroundColor = UIColor.whiteColor()
        self.view.alpha = 1.0
    }

    NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(backToOriginalBackground), userInfo: nil, repeats: false)
}

Cependant, cela indique une erreur: Argument of #selector cannot refer to a property.

Bien sûr, je peux définir une nouvelle méthode distincte et lui transférer l’implémentation de la fermeture, mais je veux la garder sobre pour une implémentation aussi petite.

Est-il possible de définir une clôture sur #selector argument?

20
Blaszard

Comme @ gnasher729 le remarque, cela n'est pas possible car les sélecteurs ne sont que des noms de méthodes, pas des méthodes elles-mêmes. Dans le cas général, j’utiliserais dispatch_after ici, mais dans ce cas particulier, le meilleur outil IMO est UIView.animateWithDuration, car c’est à quoi sert cette fonction et il est très facile d’ajuster la transition:

UIView.animateWithDuration(0, delay: 0.5, options: [], animations: {
    self.view.backgroundColor = UIColor.whiteColor()
    self.view.alpha = 1.0
}, completion: nil)
6
Rob Napier

Pas directement, mais certaines solutions de contournement sont possibles. Regardez l'exemple suivant.

/// Target-Action helper.
final class Action: NSObject {

    private let _action: () -> ()

    init(action: () -> ()) {
        _action = action
        super.init()
    }

    func action() {
        _action()
    }

}

let action1 = Action { print("action1 triggered") }

let button = UIButton()
button.addTarget(action1, action: #selector(action1.action), forControlEvents: .TouchUpInside)
21
werediver

J'ai essayé ceci pour UIBarButtonItem au moins:

private var actionKey: Void?

extension UIBarButtonItem {

    private var _action: () -> () {
        get {
            return objc_getAssociatedObject(self, &actionKey) as! () -> ()
        }
        set {
            objc_setAssociatedObject(self, &actionKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    convenience init(title: String?, style: UIBarButtonItemStyle, action: @escaping () -> ()) {
        self.init(title: title, style: style, target: nil, action: #selector(pressed))
        self.target = self
        self._action = action
    }

    @objc private func pressed(sender: UIBarButtonItem) {
        _action()
    }

}

Ensuite, vous pouvez faire ceci:

navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Test", style: .plain, action: {
    print("Hello World!")
})
7
schirrmacher

Vous pouvez utiliser ActionClosurable qui prend en charge UIControl, UIButton, UIRefreshControl, UIGestureRecognizer et UIBarButtonItem . https://github.com/takasek/ActionClosurable

Ci-dessous, exemple de UIBarButtonItem

// UIBarButtonItem
let barButtonItem = UIBarButtonItem(title: "title", style: .plain) { _ in
    print("barButtonItem title")
}
1
fatenumber25

Non, #selector fait référence à une méthode Objective-C. 

Vous pouvez cependant faire beaucoup mieux: ajoutez une extension à NSTimer qui vous permet de créer un minuteur programmé non avec une cible et un sélecteur, mais avec une fermeture. 

1
gnasher729

Si vous modifiez la portée du bloc en classe et non en fonction et maintenez une référence à la fermeture. 

Vous pouvez invoquer cette fermeture avec une fonction. dans la classe. Ainsi, vous pouvez invoquer cette fermeture en tant que sélecteur. 

Quelque chose comme ça: 

class Test: NSObject {
    let backToOriginalBackground = {

    }
    func backgroundChange() {
        NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(test), userInfo: nil, repeats: false)
    }

    func test() {
        self.backToOriginalBackground()
    }
}
0

Donc, ma réponse à l'idée d'attribuer une selector à une closure d'une manière rapide ressemble à certaines des réponses, mais je pensais partager un exemple concret de la façon dont je l'avais fait dans une extension UIViewController.

fileprivate class BarButtonItem: UIBarButtonItem {
  var actionCallback: ( () -> Void )?
  func buttonAction() {
    actionCallback?()
  }
}

fileprivate extension Selector {
  static let onBarButtonAction = #selector(BarButtonItem.buttonAction)
}

extension UIViewController {
  func createBarButtonItem(title: String, action: @escaping () -> Void ) -> UIBarButtonItem {
    let button = BarButtonItem(title: title, style: .plain, target nil, action: nil)
    button.actionCallback = action
    button.action = .onBarButtonAction
    return button
  }
}

// Example where button is inside a method of a UIViewController 
// and added to the navigationItem of the UINavigationController

let button = createBarButtonItem(title: "Done"){
  print("Do something when done")
}

navigationItem.setLeftbarButtonItems([button], animated: false)
0
Justin Wright

Ma solution était de créer une variable de bloc de classe comme:

let completionBlock: () -> () = nil

Créez une méthode qui appelle cette completionBlock:

func completed(){
    self.completionBlock!()
}

Et à l’intérieur où je veux placer mon sélecteur comme un bloc que j’ai fait:

func myFunc(){
  self.completionBlock = {//what I want to be done}
  NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(Myclass.completed), userInfo: nil, repeats: false)
}
0
Aitor Pagán