¿Puedo hacer que#selector se refiera a un cierre en Swift?
uibarbuttonitem swift 4 (9)
Ahora es posible. He creado una esencia para los selectores basados en bloques en Swift 4.
https://gist.github.com/cprovatas/98ff940140c8744c4d1f3bcce7ba4543
Uso:
UIButton().addTarget(Selector, action: Selector { debugPrint("my code here") }, for: .touchUpInside)
Quiero hacer que un argumento selector
de mi método se refiera a una propiedad de cierre, ambas existen en el mismo ámbito. Por ejemplo,
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)
}
Sin embargo, esto muestra un error: el Argument of #selector cannot refer to a property
.
Por supuesto, puedo definir un método nuevo e independiente y moverle la implementación del cierre, pero quiero mantenerlo frugal para una implementación tan pequeña.
¿Es posible establecer un cierre en el argumento #selector
?
Así que mi respuesta a tener un selector
asignado a un closure
de manera rápida es similar a algunas de las respuestas ya, pero pensé que compartiría un ejemplo de la vida real de cómo lo hice dentro de una extensión 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)
Como observa @ gnasher729, esto no es posible porque los selectores son solo nombres de métodos, no métodos en sí mismos. En el caso general, usaría dispatch_after
aquí, pero en este caso particular, la mejor herramienta de IMO es UIView.animateWithDuration
, porque es exactamente para qué sirve esa función, y es muy fácil ajustar la transición:
UIView.animateWithDuration(0, delay: 0.5, options: [], animations: {
self.view.backgroundColor = UIColor.whiteColor()
self.view.alpha = 1.0
}, completion: nil)
Intenté esto para UIBarButtonItem al menos:
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()
}
}
Entonces puedes hacer esto:
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Test", style: .plain, action: {
print("Hello World!")
})
Mi solución fue crear una variable de bloque de clase como:
let completionBlock: () -> () = nil
Cree un método que llame a este compleciónBloque:
func completed(){
self.completionBlock!()
}
Y dentro de donde quiero poner mi selector como un bloque que hice:
func myFunc(){
self.completionBlock = {//what I want to be done}
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(Myclass.completed), userInfo: nil, repeats: false)
}
No directamente, pero algunas soluciones son posibles. Echa un vistazo al siguiente ejemplo.
/// 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)
No, #selector se refiere a un método Objective-C.
Sin embargo, puede hacer algo mucho mejor: agregue una extensión a NSTimer que le permita crear un temporizador programado no con un objetivo y un selector, sino con un cierre.
Puede usar ActionClosurable que es compatible con UIControl, UIButton, UIRefreshControl, UIGestureRecognizer y UIBarButtonItem. https://github.com/takasek/ActionClosurable
A continuación se muestra un ejemplo de UIBarButtonItem.
// UIBarButtonItem
let barButtonItem = UIBarButtonItem(title: "title", style: .plain) { _ in
print("barButtonItem title")
}
Si cambia el alcance del bloque a un alcance de clase en lugar de a una función y mantiene una referencia para cerrar allí.
Podrías invocar ese cierre con una función. en la clase. Así que de esa manera puedes invocar ese cierre como un selector.
Algo como esto:
class Test: NSObject {
let backToOriginalBackground = {
}
func backgroundChange() {
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(test), userInfo: nil, repeats: false)
}
func test() {
self.backToOriginalBackground()
}
}