ios 将闭包作为目标添加到 UIButton

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25919472/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 02:35:15  来源:igfitidea点击:

Adding a closure as target to a UIButton

iosswiftuibuttonaddtarget

提问by Ilker Baltaci

I have a generic control class which needs to set the completion of the button depending on the view controller.Due to that setLeftButtonActionWithClosure function needs to take as parameter a closure which should be set as action to an unbutton.How would it be possible in Swift since we need to pass the function name as String to action: parameter.

我有一个通用控件类,它需要根据视图控制器设置按钮的完成。由于 setLeftButtonActionWithClosure 函数需要将闭包作为参数,该闭包应该设置为取消按钮的操作。在 Swift 中怎么可能因为我们需要将函数名称作为字符串传递给 action: 参数。

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

采纳答案by Armanoide

NOTE: like @EthanHuang said "This solution doesn't work if you have more than two instances. All actions will be overwrite by the last assignment."Keep in mind this when you develop, i will post another solution soon.

注意:就像@EthanHuang 说的“如果您有两个以上的实例,此解决方案不起作用。所有操作都将被最后一个分配覆盖。” 开发时请记住这一点,我将很快发布另一个解决方案。

If you want to add a closure as target to a UIButton, you must add a function to UIButtonclass by using extension

如果要将闭包作为目标添加到 a UIButton,则必须使用以下方法将函数添加到UIButtonextension

Swift 5

斯威夫特 5

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

Older

年长的

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

and the call:

和电话:

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

回答by aepryus

Similar solution to those already listed, but perhaps lighter weight:

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

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

Usage:

用法:

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

or

或者

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

Or if avoiding retain loops:

或者如果避免保留循环:

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

回答by jacob

You can effectively achieve this by subclassing UIButton:

你可以通过继承 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)
    }
}

Use:

用:

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

回答by Grant Kamin

Similar solution to those already listed, but perhaps lighter weight and doesn't rely on randomness to generate unique ids:

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

Usage:

用法:

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

回答by Le Mot Juiced

This is basically Armanoide'sanswer, above, but with a couple slight changes that are useful for me:

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

  • the passed-in closure can take a UIButtonargument, allowing you to pass in self
  • the functions and arguments are renamed in a way that, for me, clarifies what's going on, for instance by distinguishing a Swift closure from a UIButtonaction.

    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)
    }
    
  • 传入的闭包可以接受一个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)
    }
    

Much props to Armanoide though for some heavy-duty magic here.

尽管这里有一些重型魔法,但对 Armanoide 有很多道具。

回答by M. Porooshani

I have started to use Armanoide's answer disregarding the fact that it'll be overridden by the second assignment, mainly because at first I needed it somewhere specific which it didn't matter much. But it started to fall apart.

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

I've came up with a new implementation using AssicatedObjectswhich doesn't have this limitation, I think has a smarter syntax, but it's not a complete solution:

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

Here it is:

这里是:

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

    }
  }

}

As you can see, I've decided to make a dedicated case for touchUpInside. I know controls have more events than this one, but who are we kidding? do we need actions for every one of them?! It's much simpler this way.

如您所见,我决定为touchUpInside. 我知道控件有比这个更多的事件,但我们在开玩笑吗?我们需要为他们中的每一个采取行动吗?!这种方式要简单得多。

Usage example:

用法示例:

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

In any case, if you want to extend this answer you can either make a Setof actions for all the event types, or add more event's properties for other events, it's relatively straightforward.

无论如何,如果你想扩展这个答案,你可以Set为所有事件类型做一个动作,或者为其他事件添加更多事件的属性,这相对简单。

Cheers, M.

干杯,M。

回答by Musa almatri

Swift

迅速

After trying all the solutions, this one worked for me for all cases, even when the button in reusable table view cell

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

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

And then you call it like this:

然后你这样称呼它:

loginButton.addTargetClosure { _ in

   // login logics

}

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

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

回答by LightinDarknessJava

My solution.

我的解决方案。

typealias UIAction = () -> Void;

class Button: UIButton {

    public var touchUp :UIAction? {
        didSet {
            self.setup()
        }
    }

    func setup() -> Void {
        self.addTarget(self, action: #selector(touchInside), for: .touchUpInside)
    }

    @objc private func touchInside() -> Void {
        self.touchUp!()
    }

}

回答by Eric Armstrong

Swift 4.2 for UIControl and UIGestureRecognizer, and and remove targets through swift extension stored property paradigm.

Swift 4.2 用于 UIControl 和 UIGestureRecognizer,并通过 Swift 扩展存储属性范例移除目标。

Wrapper class for the selector

选择器的包装类

class Target {

    private let t: () -> ()
    init(target t: @escaping () -> ()) { self.t = t }
    @objc private func s() { t() }

    public var action: Selector {
        return #selector(s)
    }
}

Protocols with associatedtypes so we can hide hide the objc_code

带有associatedtypes 的协议,以便我们可以隐藏隐藏objc_代码

protocol PropertyProvider {
    associatedtype PropertyType: Any

    static var property: PropertyType { get set }
}

protocol ExtensionPropertyStorable: class {
    associatedtype Property: PropertyProvider
}

Extension to make the property default and available

使属性默认和可用的扩展

extension ExtensionPropertyStorable {

    typealias Storable = Property.PropertyType

    var property: Storable {
        get { return objc_getAssociatedObject(self, String(describing: type(of: Storable.self))) as? Storable ?? Property.property }
        set { return objc_setAssociatedObject(self, String(describing: type(of: Storable.self)), newValue, .OBJC_ASSOCIATION_RETAIN) }
    }
}

Let us apply the magic

让我们应用魔法

extension UIControl: ExtensionPropertyStorable {

    class Property: PropertyProvider {
        static var property = [String: Target]()
    }

    func addTarget(for controlEvent: UIControl.Event = .touchUpInside, target: @escaping () ->()) {
        let key = String(describing: controlEvent)
        let target = Target(target: target)
        addTarget(target, action: target.action, for: controlEvent)
        property[key] = target
    }

    func removeTarget(for controlEvent: UIControl.Event = .touchUpInside) {
        let key = String(describing: controlEvent)
        let target = property[key]
        removeTarget(target, action: target?.action, for: controlEvent)
        property[key] = nil
    }
}

And to the gestures

和手势

extension UIGestureRecognizer: ExtensionPropertyStorable {

    class Property: PropertyProvider {
        static var property: Target?
    }

    func addTarget(target: @escaping () -> ()) {
        let target = Target(target: target)
        addTarget(target, action: target.action)
        property = target
    }

    func removeTarget() {
        let target = property
        removeTarget(target, action: target?.action)
        property = nil
    }
}

Example usage:

用法示例:

button.addTarget {
    print("touch up inside")
}
button.addTarget { [weak self] in
    print("this will only happen once")
    self?.button.removeTarget()
}
button.addTarget(for: .touchDown) {
    print("touch down")
}
slider.addTarget(for: .valueChanged) {
    print("value changed")
}
textView.addTarget(for: .allEditingEvents) { [weak self] in
    self?.editingEvent()
}
gesture.addTarget { [weak self] in
    self?.gestureEvent()
    self?.otherGestureEvent()
    self?.gesture.removeTarget()
}

回答by Nathan F.

I put together a little extension for UIControl that will let you use closures for any action on any UIControl really easily.

我为 UIControl 组合了一个小扩展,它可以让你非常轻松地对任何 UIControl 上的任何操作使用闭包。

You can find it here: https://gist.github.com/nathan-fiscaletti/8308f00ff364b72b6a6dec57c4b13d82

你可以在这里找到它:https: //gist.github.com/nathan-fiscaletti/8308f00ff364b72b6a6dec57c4b13d82

Here are some examples of it in practice:

以下是它在实践中的一些示例:

Setting a Button Action

设置按钮操作

myButton.action(.touchUpInside, { (sender: UIControl) in
    // do something
})

Detecting a Switch changing Values

检测开关改变值

mySwitch.action(.valueChanged, { (sender: UIControl) in
    print("Switch State:", mySwitch.isOn)
})