【问题标题】:Adding a closure as target to a UIButton将闭包作为目标添加到 UIButton
【发布时间】:2014-11-13 04:41:55
【问题描述】:

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

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

【问题讨论】:

    标签: ios swift uibutton addtarget


    【解决方案1】:

    不要使用此答案,请参阅下面的注释

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

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

    斯威夫特 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)
        }
    }
    

    老年人

    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: .touchUpInside, 
     ForAction:{() -> Void in
         print("Touch")
     })
    

    【讨论】:

    • 完美,这是一个不错的解决方案。
    • 这太棒了。设置结构如何工作?那是一个全局结构还是什么?为什么它的“动作”值在函数调用之间持续存在?我为自己制作了一个带有重命名功能的版本,以澄清我自己的情况,发布在下面。
    • 你错过了UIButton 扩展中的右大括号。
    • 如果您将操作处理程序添加到同一个 UIButton 上的多个 UIControlEvents,此解决方案将不起作用。第二个将覆盖第一个的处理程序。
    • 如果您有两个以上的实例,此解决方案将不起作用。所有操作都将被最后一次分配覆盖。
    【解决方案2】:

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

    • 传入的闭包可以带一个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 有很多道具。

    【讨论】:

    • 如果我在一个屏幕上添加两个具有不同闭包的不同按钮,所有按钮将从最后创建的按钮执行闭包。我做错了还是有什么可以处理的?
    【解决方案3】:

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

    用途:

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

    【讨论】:

      【解决方案4】:

      我已经开始使用 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.

      【讨论】:

        【解决方案5】:

        Apple 终于在 iOS 14 中将此功能添加到 UIKit。但是,由于 Apple 的方法签名不是最理想的,因此有人可能仍想使用此扩展。

        iOS 14

        extension UIControl {
            func addAction(for controlEvents: UIControl.Event = .touchUpInside, _ closure: @escaping()->()) {
                addAction(UIAction { (action: UIAction) in closure() }, for: controlEvents)
            }
        }
        

        iOS 14 之前的版本

        extension UIControl {
            func addAction(for controlEvents: UIControl.Event = .touchUpInside, _ closure: @escaping()->()) {
                @objc class ClosureSleeve: NSObject {
                    let closure:()->()
                    init(_ closure: @escaping()->()) { self.closure = closure }
                    @objc func invoke() { closure() }
                }
                let sleeve = ClosureSleeve(closure)
                addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
                objc_setAssociatedObject(self, "\(UUID())", sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
            }
        }
        

        用法:

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

        或:

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

        或者如果避免保留循环:

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

        (此处包含扩展名:https://github.com/aepryus/Acheron

        还要注意,理论上 .primaryActionTriggered 可以替代 .touchUpInside,但它目前似乎在催化剂中存在问题,所以我暂时保留它。

        【讨论】:

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

        还有一个优化(如果您在许多地方使用它并且不想重复调用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
                )
            }
        }
        

        【讨论】:

        • 这似乎与上面对 objc_setAssociatedObject 的调用次数完全相同。
        • 嗨@aepryus 只有一个细微的区别——假设你想将它用于UIBarButtonItemUIButton。在您的实现中,您需要创建一个sleeve 并调用objc_setAssociatedObject(self, String(format: "[%d]", arc4random()), sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) 两次。在我的改进中你不需要调用后者(它由ClosureSleeve的构造函数处理)
        • 如果您将同一个 ClosureSleeve 附加到两个不同的控件,您实际上想要调用它两次,以便每个都对 ClosureSleeve 具有强引用,并且直到您的两个控件中的每一个都不会释放它控件也被释放。
        • @aepryus 我不确定我是否正确...我并不是要将ClosureSleeve 附加到两个不同的对象(在我的解决方案中甚至不可能,因为你正在附加它在构造函数中)。请检查我的更新答案
        • @aepryus 很高兴听到这个消息!实际上,这是对您原始解决方案的一个小改进。原来是你的吗?如果是我不介意你用我的回答来改进你的回答?
        【解决方案7】:

        斯威夫特

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

        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

        【讨论】:

          【解决方案8】:
          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") })
          

          【讨论】:

            【解决方案9】:

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

            或者如果避免保留循环:

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

            【讨论】:

            • 从未使用过捕获“自我”警告
            【解决方案10】:

            我的解决方案。

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

            【讨论】:

              【解决方案11】:

              用于 UIControl 和 UIGestureRecognizer 的 Swift 4.2,以及通过 swift 扩展存储属性范例删除目标。

              选择器的包装类

              class Target {
              
                  private let t: () -> ()
                  init(target t: @escaping () -> ()) { self.t = t }
                  @objc private func s() { t() }
              
                  public var action: Selector {
                      return #selector(s)
                  }
              }
              

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

              protocol PropertyProvider {
                  associatedtype PropertyType: Any
              
                  static var property: PropertyType { get set }
              }
              
              protocol ExtensionPropertyStorable: class {
                  associatedtype Property: PropertyProvider
              }
              

              使属性默认可用的扩展

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

              让我们施展魔法

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

              还有手势

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

              示例用法:

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

              【讨论】:

              • 我发现导致错误的原因是这个键 objc_getAssociatedObject(self, String(describing: type(of: Storable.self))) 您应该将它们更改为 &AssociatedKeys.storable 和 AssociatedKeys 之类的东西具有静态字段 var 可存储的结构。否则访问此属性对象时会出现问题,并且很多时候它会返回 nil,因为字符串可以与指针不同地解释!
              【解决方案12】:

              @Armanoide 解决方案很酷,因为它在其中使用了 structstatic var 的技巧,但如果您多次重复使用一个按钮,它并不完美,因为在这种情况下,动作关闭将始终存储最后一个处理程序。

              我已经为 UIKitPlus 库修复了它

              import UIKit
              
              extension UIControl {
                  private func actionHandler(action: (() -> Void)? = nil) {
                      struct Storage { static var actions: [Int: (() -> Void)] = [:] }
                      if let action = action {
                          Storage.actions[hashValue] = action
                      } else {
                          Storage.actions[hashValue]?()
                      }
                  }
              
                  @objc func triggerActionHandler() {
                      actionHandler()
                  }
              
                  func actionHandler(controlEvents control: UIControl.Event, forAction action: @escaping () -> Void) {
                      actionHandler(action: action)
                      addTarget(self, action: #selector(triggerActionHandler), for: control)
                  }
              }
              

              【讨论】:

                【解决方案13】:

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

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

                以下是一些实践中的例子:

                设置按钮操作

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

                检测开关改变值

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

                【讨论】:

                  【解决方案14】:

                  这是一个很好的框架:HandlersKit。最大的好处是你可以访问闭包内的发送者而不需要类型转换或可选的解包。

                  UIButton 示例:

                  import HandlersKit
                  
                  let button = MyActivityIndicatorButton()
                  button.onTap { (sender: MyActivityIndicatorButton) in
                      sender.showActivityIndicator()
                  }
                  

                  UISwitch 示例:

                  let switchView = UISwitch(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 50.0))
                  switchView.onChange { isOn in
                      print("SwitchView is: \(isOn)")
                  }
                  

                  【讨论】:

                    【解决方案15】:

                    这是一个通用的 swift 5 方法。它在动作块中有一个发送者,并且消除了两次为同一事件添加动作

                    import UIKit
                    
                    protocol Actionable {
                        associatedtype T = Self
                        func addAction(for controlEvent: UIControl.Event, action: ((T) -> Void)?)
                    }
                    
                    private class ClosureSleeve<T> {
                        let closure: ((T) -> Void)?
                        let sender: T
                    
                        init (sender: T, _ closure: ((T) -> Void)?) {
                            self.closure = closure
                            self.sender = sender
                        }
                    
                        @objc func invoke() {
                            closure?(sender)
                        }
                    }
                    
                    extension Actionable where Self: UIControl {
                        func addAction(for controlEvent: UIControl.Event, action: ((Self) -> Void)?) {
                            let previousSleeve = objc_getAssociatedObject(self, String(controlEvent.rawValue))
                            objc_removeAssociatedObjects(previousSleeve as Any)
                            removeTarget(previousSleeve, action: nil, for: controlEvent)
                    
                            let sleeve = ClosureSleeve(sender: self, action)
                            addTarget(sleeve, action: #selector(ClosureSleeve<Self>.invoke), for: controlEvent)
                            objc_setAssociatedObject(self, String(controlEvent.rawValue), sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
                        }
                    }
                    
                    extension UIControl: Actionable {}
                    

                    【讨论】:

                      【解决方案16】:

                      这现在可以在iOS 14 上实现。您可以在创建UIButton 时传递一个UIAction,它有一个处理程序闭包:

                      let action = UIAction(title: "") { action in
                          print("Button tapped!")
                      }
                      
                      UIButton(type: .system, primaryAction: action)
                      

                      或更短:

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

                      【讨论】:

                        【解决方案17】:

                        这是aepryusanswer 的有趣变体。我的版本使用 Combine 的 Cancellable 协议来:

                        1. 支持移除已注册的闭包。
                        2. 处理内存管理,从而避免使用objc_setAssociatedObject
                        // Swift 5
                        
                        import Combine
                        import UIKit
                        
                        class BlockObject: NSObject {
                            let block: () -> Void
                        
                            init(block: @escaping () -> Void) {
                                self.block = block
                            }
                        
                            @objc dynamic func execute() {
                                block()
                            }
                        }
                        
                        extension UIControl {
                            func addHandler(
                                for controlEvents: UIControl.Event,
                                block: @escaping () -> Void)
                                -> Cancellable
                            {
                                let blockObject = BlockObject(block: block)
                                addTarget(blockObject, action: #selector(BlockObject.execute), for: controlEvents)
                        
                                return AnyCancellable {
                                    self.removeTarget(blockObject, action: #selector(BlockObject.execute), for: controlEvents)
                                }
                            }
                        }
                        

                        用法:

                        let button = UIButton(type: .system)
                        
                        // Add the handler
                        let cancellable = button.addHandler(for: .touchUpInside) {
                            print("Button pressed!")
                        }
                        
                        // Remove the handler
                        cancellable.cancel()
                        

                        不要忘记存储对Cancellable 的引用,否则处理程序将立即注销。

                        【讨论】:

                          【解决方案18】:

                          下面的扩展是为 UIView 的级别添加点击手势,这将适用于任何基于 UIView 的东西。

                          注意:几年前我也在 StackOverflow 上找到了这个解决方案,但现在我似乎找不到原始来源。

                          extension UIView {
                              
                              // In order to create computed properties for extensions, we need a key to
                              // store and access the stored property
                              fileprivate struct AssociatedObjectKeys {
                                  static var tapGestureRecognizer = "MediaViewerAssociatedObjectKey_mediaViewer"
                              }
                              
                              fileprivate typealias Action = (() -> Void)?
                              
                              // Set our computed property type to a closure
                              fileprivate var tapGestureRecognizerAction: Action? {
                                  set {
                                      if let newValue = newValue {
                                          // Computed properties get stored as associated objects
                                          objc_setAssociatedObject(self, &AssociatedObjectKeys.tapGestureRecognizer, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
                                      }
                                  }
                                  get {
                                      let tapGestureRecognizerActionInstance = objc_getAssociatedObject(self, &AssociatedObjectKeys.tapGestureRecognizer) as? Action
                                      return tapGestureRecognizerActionInstance
                                  }
                              }
                              
                              // This is the meat of the sauce, here we create the tap gesture recognizer and
                              // store the closure the user passed to us in the associated object we declared above
                              public func addTapGestureRecognizer(action: (() -> Void)?) {
                                  self.isUserInteractionEnabled = true
                                  self.tapGestureRecognizerAction = action
                                  let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture))
                                  tapGestureRecognizer.cancelsTouchesInView = false
                                  self.addGestureRecognizer(tapGestureRecognizer)
                              }
                              
                              // Every time the user taps on the UIImageView, this function gets called,
                              // which triggers the closure we stored
                              @objc fileprivate func handleTapGesture(sender: UITapGestureRecognizer) {
                                  if let action = self.tapGestureRecognizerAction {
                                      action?()
                                  } else {
                                      print("no action")
                                  }
                              }
                              
                          }
                          

                          使用示例:

                          let button = UIButton()
                          button.addTapGestureRecognizer {
                              print("tapped")
                          }
                                  
                          let label = UILabel()
                          label.addTapGestureRecognizer {
                              print("label tapped")
                          }           
                          

                          【讨论】:

                            【解决方案19】:

                            我更改了@Nathan F 发布的 UIControl 的一个小扩展。 here

                            我使用objc_setAssociatedObjectobjc_getAssociatedObject 来获取/设置闭包,并使用所有创建的按钮的键删除了全局静态变量。 所以现在为每个实例存储事件并在 dealloc 后释放

                            extension UIControl {
                                
                                typealias Handlers = [UInt:((UIControl) -> Void)]
                                
                                private enum AssociatedKey {
                                    static var actionHandlers = "UIControl.actionHandlers"
                                }
                            
                                /**
                                 * A map of closures, mapped as  [ event : action ] .
                                 */
                            
                                private var actionHandlers: Handlers {
                                    get {
                                        return objc_getAssociatedObject(self, &AssociatedKey.actionHandlers) as? Handlers ?? [:]
                                    }
                                    set(newValue) {
                                        objc_setAssociatedObject(self, &AssociatedKey.actionHandlers, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
                                    }
                                }
                            }
                            

                            你可以在这里找到它:https://gist.github.com/desyatov/6ed83de58ca1146d85fedab461a69b12

                            这里有一些例子:

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

                            【讨论】:

                              猜你喜欢
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 2020-05-13
                              • 1970-01-01
                              • 1970-01-01
                              • 2014-07-15
                              • 1970-01-01
                              • 1970-01-01
                              相关资源
                              最近更新 更多