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 argumentoUIButton
, permitiéndole pasar enself
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
)
}
}