ios swift uibutton addtarget

ios - Agregar un cierre como destino a un UIButton



swift addtarget (6)

Puedes lograr esto de manera efectiva subclasando UIButton:

class ActionButton: UIButton { var touchDown: ((button: UIButton) -> ())? var touchExit: ((button: UIButton) -> ())? var touchUp: ((button: UIButton) -> ())? required init?(coder aDecoder: NSCoder) { fatalError("init(coder:)") } override init(frame: CGRect) { super.init(frame: frame) setupButton() } func setupButton() { //this is my most common setup, but you can customize to your liking addTarget(self, action: #selector(touchDown(_:)), forControlEvents: [.TouchDown, .TouchDragEnter]) addTarget(self, action: #selector(touchExit(_:)), forControlEvents: [.TouchCancel, .TouchDragExit]) addTarget(self, action: #selector(touchUp(_:)), forControlEvents: [.TouchUpInside]) } //actions func touchDown(sender: UIButton) { touchDown?(button: sender) } func touchExit(sender: UIButton) { touchExit?(button: sender) } func touchUp(sender: UIButton) { touchUp?(button: sender) } }

Utilizar:

let button = ActionButton(frame: buttonRect) button.touchDown = { button in print("Touch Down") } button.touchExit = { button in print("Touch Exit") } button.touchUp = { button in print("Touch Up") }

Tengo una clase de control genérica que necesita establecer la finalización del botón dependiendo del controlador de visualización. Debido a que la función setLeftButtonActionWithClosure necesita tomar como parámetro un cierre que se debe establecer como acción para un botón. ¿Cómo sería posible en Swift? ya que necesitamos pasar el nombre de la función como String a action: parameter.

func setLeftButtonActionWithClosure(completion: () -> Void) { self.leftButton.addTarget(<#target: AnyObject?#>, action: <#Selector#>, forControlEvents: <#UIControlEvents#>) }


NOTA: como @EthanHuang dijo "Esta solución no funciona si tienes más de dos instancias. Todas las acciones se sobrescribirán en la última asignación". Tenga en cuenta que cuando se desarrolle, publicaré otra solución pronto.

Si desea agregar un cierre como destino a un UIButton , debe agregar una función a la clase UIButton utilizando la extension

import UIKit extension UIButton { private func actionHandleBlock(action:(() -> Void)? = nil) { struct __ { static var action :(() -> Void)? } if action != nil { __.action = action } else { __.action?() } } @objc private func triggerActionHandleBlock() { self.actionHandleBlock() } func actionHandle(controlEvents control :UIControlEvents, ForAction action:() -> Void) { self.actionHandleBlock(action) self.addTarget(self, action: "triggerActionHandleBlock", forControlEvents: control) } }

y la llamada:

let button = UIButton() button.actionHandle(controlEvents: UIControlEvents.TouchUpInside, ForAction:{() -> Void in print("Touch") })


Empecé a usar la respuesta de Armanoide sin tener en cuenta el hecho de que la segunda asignación la anulará , principalmente porque al principio la necesitaba en algún lugar específico, que no importaba demasiado. Pero comenzó a desmoronarse.

He creado una nueva implementación usando AssicatedObjects que no tiene esta limitación, creo que tiene una sintaxis más inteligente, pero no es una solución completa:

Aquí está:

typealias ButtonAction = () -> Void fileprivate struct AssociatedKeys { static var touchUp = "touchUp" } fileprivate class ClosureWrapper { var closure: ButtonAction? init(_ closure: ButtonAction?) { self.closure = closure } } extension UIControl { @objc private func performTouchUp() { guard let action = touchUp else { return } action() } var touchUp: ButtonAction? { get { let closure = objc_getAssociatedObject(self, &AssociatedKeys.touchUp) guard let action = closure as? ClosureWrapper else{ return nil } return action.closure } set { if let action = newValue { let closure = ClosureWrapper(action) objc_setAssociatedObject( self, &AssociatedKeys.touchUp, closure as ClosureWrapper, .OBJC_ASSOCIATION_RETAIN_NONATOMIC ) self.addTarget(self, action: #selector(performTouchUp), for: .touchUpInside) } else { self.removeTarget(self, action: #selector(performTouchUp), for: .touchUpInside) } } } }

Como puede ver, he decidido hacer un caso dedicado para touchUpInside . Sé que los controles tienen más eventos que este, pero ¿a quién engañamos? ¿Necesitamos acciones para cada uno de ellos? Es mucho más simple de esta manera.

Ejemplo de uso:

okBtn.touchUp = { print("OK") }

En cualquier caso, si desea extender esta respuesta, puede realizar un Set de acciones para todos los tipos de eventos o agregar más propiedades de eventos para otros eventos, es relativamente sencillo.

Cheers, M.


Solución similar a las ya enumeradas, pero quizás más ligero:

class ClosureSleeve { let closure: ()->() init (_ closure: @escaping ()->()) { self.closure = closure } @objc func invoke () { closure() } } extension UIControl { func add (for controlEvents: UIControlEvents, _ closure: @escaping ()->()) { let sleeve = ClosureSleeve(closure) addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents) objc_setAssociatedObject(self, String(format: "[%d]", arc4random()), sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) } }

Uso:

button.add(for: .touchUpInside) { print("Hello, Closure!") }


Esta es básicamente la respuesta de Armanoide , arriba, pero con un par de ligeros cambios que son útiles para mí:

  • el cierre UIButton -in puede tomar un argumento UIButton , permitiéndole pasar en self
  • las funciones y los argumentos se renombran de una manera que, para mí, aclara lo que está sucediendo, por ejemplo, al distinguir un cierre Swift de una acción UIButton .

    private func setOrTriggerClosure(closure:((button:UIButton) -> Void)? = nil) { //struct to keep track of current closure struct __ { static var closure :((button:UIButton) -> Void)? } //if closure has been passed in, set the struct to use it if closure != nil { __.closure = closure } else { //otherwise trigger the closure __. closure?(button: self) } } @objc private func triggerActionClosure() { self.setOrTriggerClosure() } func setActionTo(closure:(UIButton) -> Void, forEvents :UIControlEvents) { self.setOrTriggerClosure(closure) self.addTarget(self, action: #selector(UIButton.triggerActionClosure), forControlEvents: forEvents) }

Sin embargo, hay muchos accesorios para Armanoide para la magia de alto rendimiento aquí.


Una optimización más (útil si la usa en muchos lugares y no desea duplicar la llamada a objc_setAssociatedObject ). Nos permite no preocuparnos por una parte sucia de objc_setAssociatedObject y la mantiene dentro del constructor de ClosureSleeve :

class ClosureSleeve { let closure: () -> Void init( for object: AnyObject, _ closure: @escaping () -> Void ) { self.closure = closure objc_setAssociatedObject( object, String(format: "[%d]", arc4random()), self, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN ) } @objc func invoke () { closure() } }

Así que su extensión se verá un poco más limpia:

extension UIControl { func add( for controlEvents: UIControlEvents, _ closure: @escaping ()->() ) { let sleeve = ClosureSleeve( for: self, closure ) addTarget( sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents ) } }