将闭包作为目标添加到UIButton

我有一个通用的控件类,需要根据视图控制器来设置按钮的完成。由于setLeftButtonActionWithClosure函数需要将一个闭包作为参数,该闭包应该被设置为解开的动作。如何在Swift中可以实现因为我们需要将函数名称作为String传递给action:参数。

func setLeftButtonActionWithClosure(completion: () -> Void) { self.leftButton.addTarget(, action: , forControlEvents: ) } 

注意:像@EthanHuang说的那样“如果你有两个以上的实例,这个解决方案就不起作用了。所有的操作都会被最后一个任务覆盖。” 开发时请记住这一点,我会尽快发布另一种解决方案。

如果要将闭包作为目标添加到UIButton ,则必须使用extensionUIButton类添加函数

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

和电话:

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

类似的解决方案已经列出,但可能更轻:

 class ClosureSleeve { let closure: ()->() init (_ closure: @escaping ()->()) { self.closure = closure } @objc func invoke () { closure() } } extension UIControl { func addAction(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) } } 

用法:

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

或者如果避免保留循环:

 self.button.addAction(for: .touchUpInside) { [weak self] in self?.doStuff() } 

您可以通过inheritanceUIButton来有效地实现这一目标:

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

使用:

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

这基本上是Armanoide的答案,但有一些对我有用的微小变化:

  • 传入的闭包可以采用UIButton参数,允许你传入self
  • 函数和参数的重命名方式,对我来说,澄清了正在发生的事情,例如通过区分Swift闭包和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) } 

虽然在这里有一些重型魔法,但Armanoide的道具很多。

我已经开始使用Armanoide的答案,而忽略了它将被第二次任务覆盖的事实,主要是因为起初我需要它在某个特定的地方,这并不重要。 但它开始分崩离析。

我想出了一个使用AssicatedObjects的新实现,它没有这个限制,我认为它有一个更聪明的语法,但它不是一个完整的解决方案:

这里是:

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

如您所见,我决定为touchUpInside制作专用案例。 我知道控件比这个更多的事件,但我们开玩笑的是谁? 我们每个人都需要采取行动吗?! 这种方式简单得多。

用法示例:

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

在任何情况下,如果您想扩展此答案,您可以为所有事件类型创建一Set操作,或者为其他事件添加更多事件的属性,这是相对简单的。

干杯,M。

迅速

在尝试了所有解决方案之后,这个解决了所有情况,即使在可重复使用的表格视图单元格中的按钮时也是如此

 import UIKit typealias UIButtonTargetClosure = UIButton -> () class ClosureWrapper: NSObject { let closure: UIButtonTargetClosure init(_ closure: UIButtonTargetClosure) { self.closure = closure } } extension UIButton { private struct AssociatedKeys { static var targetClosure = "targetClosure" } private var targetClosure: UIButtonTargetClosure? { get { guard let closureWrapper = objc_getAssociatedObject(self, &AssociatedKeys.targetClosure) as? ClosureWrapper else { return nil } return closureWrapper.closure } set(newValue) { guard let newValue = newValue else { return } objc_setAssociatedObject(self, &AssociatedKeys.targetClosure, ClosureWrapper(newValue), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } func addTargetClosure(closure: UIButtonTargetClosure) { targetClosure = closure addTarget(self, action: #selector(UIButton.closureAction), forControlEvents: .TouchUpInside) } func closureAction() { guard let targetClosure = targetClosure else { return } targetClosure(self) } } 

然后你这样称呼它:

 loginButton.addTargetClosure { _ in // login logics } 

资源: https : //medium.com/@jackywangdeveloper/swift-the-right-way-to-add-target-in-uibutton-in-using-closures-877557ed9455

与已经列出的类似的解决方案,但可能更轻,并且不依赖于随机性来生成唯一ID:

 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(ObjectIdentifier(self).hashValue) + String(controlEvents.rawValue), sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) } } 

用法:

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

还有一个优化(如果您在很多地方使用它并且不想复制对objc_setAssociatedObject调用,则objc_setAssociatedObject )。 它允许我们不用担心objc_setAssociatedObject的脏部分并将其保存在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() } } 

所以你的扩展看起来会更清洁:

 extension UIControl { func add( for controlEvents: UIControlEvents, _ closure: @escaping ()->() ) { let sleeve = ClosureSleeve( for: self, closure ) addTarget( sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents ) } } 
 class ViewController : UIViewController { var aButton: UIButton! var assignedClosure: (() -> Void)? = nil override func loadView() { let view = UIView() view.backgroundColor = .white aButton = UIButton() aButton.frame = CGRect(x: 95, y: 200, width: 200, height: 20) aButton.backgroundColor = UIColor.red aButton.addTarget(self, action: .buttonTapped, for: .touchUpInside) view.addSubview(aButton) self.view = view } func fizzleButtonOn(events: UIControlEvents, with: @escaping (() -> Void)) { assignedClosure = with aButton.removeTarget(self, action: .buttonTapped, for: .allEvents) aButton.addTarget(self, action: .buttonTapped, for: events) } @objc func buttonTapped() { guard let closure = assignedClosure else { debugPrint("original tap") return } closure() } } fileprivate extension Selector { static let buttonTapped = #selector(ViewController.buttonTapped) } 

然后在应用程序生命周期的某个时刻,您将改变实例的闭包。 这是一个例子

 fizzleButtonOn(events: .touchUpInside, with: { debugPrint("a new tap action") }) 
Interesting Posts