将闭包作为目标添加到UIButton

Ilk*_*aci 34 uibutton ios swift addtarget

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

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

aep*_*yus 89

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

@objc class ClosureSleeve: NSObject {
    let closure: ()->()

    init (_ closure: @escaping ()->()) {
        self.closure = closure
    }

    @objc func invoke () {
        closure()
    }
}

extension UIControl {
    func addAction(for controlEvents: UIControl.Event = .touchUpInside, _ closure: @escaping ()->()) {
        let sleeve = ClosureSleeve(closure)
        addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
        objc_setAssociatedObject(self, "[\(arc4random())]", sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

button.addAction {
    print("Hello, Closure!")
}
Run Code Online (Sandbox Code Playgroud)

或者如果避免保留循环:

button.addAction(for: .touchUpInside) {
    print("Hello, Closure!")
}
Run Code Online (Sandbox Code Playgroud)

用户MH175提到如果他们使用控件上的"allTargets属性:静态Set_unnconditionalBridgeFromObjectiveC(_ :) - ",将会获得运行时异常.从NSObject扩展ClosureSleeve将解决此问题:

self.button.addAction(for: .touchUpInside) { [unowned self] in
    self.doStuff()
}
Run Code Online (Sandbox Code Playgroud)

  • 这可以通过覆盖先前的操作来创建问题.应该是接受的答案. (5认同)
  • arc4random() 是否保证每次都返回唯一的数字?如果您在同一个实例上多次调用“addAction”,这可能不安全,因为理论上两个随机数可能会发生冲突。使用“UUID()”作为关联的引用键来保证唯一性不是更安全吗? (4认同)
  • 我喜欢这个解决方案,但是将"UIButton"更改为"UIControl",以便它可以在所有控件上使用.:) (3认同)
  • 你能解释为什么我们需要最后一行吗?objc_setAssociatedObject ... (3认同)
  • @BohdanSavych在调用此add方法时创建了ClosureSleeve的实例.我们希望在UIControl的生命周期内保留该实例.obj_setAssociatedObject就是这样做的; 它实质上是在运行时向控件添加一个强大的属性. (2认同)

Arm*_*ide 29

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

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

import UIKit    
extension UIButton {
    private func actionHandler(action:(() -> Void)? = nil) {
        struct __ { static var action :(() -> Void)? }
        if action != nil { __.action = action }
        else { __.action?() }
    }   
    @objc private func triggerActionHandler() {
        self.actionHandler()
    }   
    func actionHandler(controlEvents control :UIControl.Event, ForAction action:@escaping () -> Void) {
        self.actionHandler(action: action)
        self.addTarget(self, action: #selector(triggerActionHandler), for: control)
    }
}
Run Code Online (Sandbox Code Playgroud)

和电话:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 如果您有两个以上的实例,则此解决方案不起作用.所有操作都将被最后一次分配覆盖. (18认同)
  • 如果将动作处理程序添加到同一UIButton上的多个UIControlEvent,则此解决方案不起作用.第二个将覆盖第一个处理程序. (10认同)

Jac*_*llo 14

您可以通过继承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)
    }
}
Run Code Online (Sandbox Code Playgroud)

使用:

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")
}
Run Code Online (Sandbox Code Playgroud)


Hej*_*azi 10

现在,这在iOS 14上已成为可能。UIAction当您创建 时,您可以传递一个具有处理程序闭包的UIButton

let action = UIAction(title: "") { action in
    print("Button tapped!")
}

UIButton(type: .system, primaryAction: action)
Run Code Online (Sandbox Code Playgroud)

或者更短:

UIButton(type: .system, primaryAction: UIAction(title: "") { action in
    print("Button tapped!")
})
Run Code Online (Sandbox Code Playgroud)


Gra*_*min 7

与已经列出的解决方案类似,但重量可能更轻,并且不依赖于随机性来生成唯一 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)
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

button.add(for: .touchUpInside) {
    print("Hello, Closure!")
}
Run Code Online (Sandbox Code Playgroud)

或者如果避免保留循环:

button.add(for: .touchUpInside) { [unowned self] in
    self.doStuff()
}
Run Code Online (Sandbox Code Playgroud)