2016-05-02 28 views
15

Voglio rendere un argomento selector del mio metodo riferimento a una proprietà di chiusura, entrambi esistono nello stesso ambito. Ad esempio,Posso fare #selector riferirsi a una chiusura in Swift?

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

Tuttavia, questa mostra un errore: Argument of #selector cannot refer to a property.

Ovviamente posso definire un nuovo metodo separato e spostare l'implementazione della chiusura su di esso, ma voglio mantenerlo frugale per un'implementazione così piccola.

È possibile impostare un argomento di chiusura su #selector?

risposta

4

Come @ gnasher729 note, questo non è possibile perché i selettori sono solo i nomi dei metodi, non i metodi stessi. Nel caso generale, userei dispatch_after qui, ma in questo caso particolare, lo strumento migliore IMO è UIView.animateWithDuration, perché è esattamente ciò che la funzione è per, ed è molto facile da modificare la transizione:

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

Sì! Questo è tutto. E stavamo riflettendo sulla sintassi piuttosto che sul problema che l'OP stava affrontando ed è la soluzione migliore. –

+0

Si prega di vedere il mio post qui sotto. È possibile con qualche magia objc-runtime :) –

1

No, #selector fa riferimento a un metodo Objective-C.

Si può fare qualcosa di molto meglio: aggiungi un'estensione a NSTimer che ti consente di creare un timer programmato non con un target e un selettore, ma con una chiusura.

+0

Sarei interessato a vedere come si presenta. Sarebbe utile in tanti modi. – ryantxr

+0

Creare quell'obiettivo è un po 'complicato perché "NSTimer" mantiene il suo "bersaglio" ma è sicuramente possibile. Meglio usare 'dispatch_after' però. – Sulthan

18

Non direttamente, ma sono possibili alcuni soluzioni. Dai un'occhiata al seguente esempio.

/// 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) 
0

Se si modifica l'ambito del blocco su un ambito di classe anziché su una funzione e si tiene un riferimento alla chiusura.

È possibile richiamare tale chiusura con una funzione. in classe. In questo modo puoi invocare quella chiusura come selettore.

Qualcosa di simile a questo:

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

la mia soluzione era quella di creare una variabile di blocco di classe come:

let completionBlock:() ->() = nil 

creare un metodo che chiama questo completionBlock:

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

E in lato dove voglio mettere il mio selettore come un blocco che ho fatto:

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

Quindi la mia risposta ad avere un selector essere assegnato a un closure in una rapida come il modo è simile ad alcuni dei già le risposte, ma ho pensato Vorrei condividere un esempio di vita reale di come l'ho fatto all'interno di un'estensione 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) 
1

ho provato questo per UIBarButtonItem almeno:

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

} 

allora si può fare questo:

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

Questo è molto utile e piacevole da mantenere come implementazione Swift. Grazie – dmclean