Adding a closure as target to a UIButton

2019-01-10 21:08发布

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.

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

11条回答
Anthone
2楼-- · 2019-01-10 21:14

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

class ClosureSleeve {
    let closure: ()->()

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

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

extension UIControl {
    func addAction(for controlEvents: UIControl.Event, _ 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)
    }
}

Usage:

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

Or if avoiding retain loops:

self.button.addAction(for: .touchUpInside) { [weak self] in
    self?.doStuff()
}
查看更多
Luminary・发光体
3楼-- · 2019-01-10 21:14

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/@jackywangdeveloper/swift-the-right-way-to-add-target-in-uibutton-in-using-closures-877557ed9455

查看更多
虎瘦雄心在
4楼-- · 2019-01-10 21:15

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

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!")
}
查看更多
家丑人穷心不美
5楼-- · 2019-01-10 21:15

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

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

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()
}
查看更多
萌系小妹纸
6楼-- · 2019-01-10 21:17

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

}
查看更多
闹够了就滚
7楼-- · 2019-01-10 21:25
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)
}

Then at some point in your app's lifecycle, you'll mutate the instances' closure. Here's an example

fizzleButtonOn(events: .touchUpInside, with: { debugPrint("a new tap action") })
查看更多
登录 后发表回答