-2

The question is: how can I do to make a generic version of UITapGestureRecognizer that I could use across my app.

If I don't want to pass any parameter, it is pretty straightforward:

class ClickListener: UITapGestureRecognizer {
    var onClick : (() -> Void)? = nil
}


// MARK: UIView Extension
extension UIView {
    
    func setOnClickListener(action :@escaping () -> Void){
        let tapRecogniser = ClickListener(target: self, action: #selector(onViewClicked(sender:)))
        tapRecogniser.onClick = action
        self.addGestureRecognizer(tapRecogniser)
    }
    
    @objc func onViewClicked(sender: ClickListener) {
        if let onClick = sender.onClick {
            onClick()
        }
    }
  
}

As Sunneet Agrawal did.

The thing is, I sometimes need to pass parameters to this function. For example, if I want to pass an Object, I would like to do so. And then I could reuse this function globally through the app.

Everything I tried didn't work, I can provide a bit of code like this:

class ClickListener<T: Any>: UITapGestureRecognizer {
    var onClick : (() -> Void)? = nil
    var clickedObject: Any
    
    override init(onClick: (() -> Void), clickedObject: Any, target: self, action: ???){
        self.onClick = onClick
        self.clickedObject = clickedObject
        super.init(target: self, action: ???)
    }
}

extension UIView {
    
    func setOnClickListener(action :@escaping () -> Void){
        let tapRecogniser = ClickListener(// Init here)
        tapRecogniser.onClick = action( // parameter here)
        self.addGestureRecognizer(tapRecogniser)
    }
    
    @objc func onViewClicked(sender: ClickListener) {
        if let onClick = sender.onClick {
            onClick(// with parameter passed)
        }
    }
  
}

// Then in my VC

UIView.setOnclickListener(action: anyFunction, clickedObject: anyObject)

But I'm a bit lost right there.

Mathieu Rios
  • 350
  • 1
  • 17
  • 1
    Parameters to what function? To the target/action that the tap gesture recognizer invokes? To the 'onClick' closure that you have added to your custom gesture recognizer? There are solutions to both of those. – Duncan C Jun 03 '21 at 12:43
  • 1
    It seems odd to me to add a closure to the tap gesture recognizer, and have the tap gesture recognizer send an action to it's target, which then invokes the closure on the gesture recognizer. – Duncan C Jun 03 '21 at 12:44
  • I tried to add more content to my question so it can be more understandable. To be clear, I want to be able to add tap gesture recognizer to UIViews and pass parameters to it. As a exemple, I would like to ba able to add a tap function to an UIView that passes an user object, then I could use a particular variable of that object in the function – Mathieu Rios Jun 03 '21 at 12:57

1 Answers1

2

First of all, the selector of the target/action pattern has two fixed forms:

  • A function without parameter
  • A function with one parameter representing the sender, the object which triggered the action.

But you can add properties in a subclass and pass the parameters in the (custom) init method.

A generic in a subclass fights the framework therefore a custom dictionary (you could use just Any, too) is the better choice.

For example beside target and action the class has an userInfo dictionary and a onClick closure. The init method calls super to call the designated initializer of the tap recognizer.

class ClickListener: UITapGestureRecognizer {
    var onClick : (() -> Void)?
    var userInfo: [String:Any]?
    
    init(target: Any?, action: Selector?, userInfo: [String:Any]? = nil, onClick: (() -> Void)? = nil) {
        self.userInfo = userInfo
        self.onClick = onClick
        super.init(target: target, action: action)
    }
}

And you can use it

extension UIView {
    
    func setOnClickListener(action :@escaping () -> Void){
        let tapRecogniser = ClickListener(target: self, action: #selector(onViewClicked), userInfo: ["message":"Hello World"], onClick: action)
        self.addGestureRecognizer(tapRecogniser)
    }
    
    @objc func onViewClicked(_ sender: ClickListener) {
        if let userInfo = sender.userInfo,
            let message = userInfo["message"] as? String {
            print(message)
        }
        sender.onClick?()
    }
    
}

A more generic implementation is to pass the userInfo in the onClick closure.

class ClickListener: UITapGestureRecognizer {
    var onClick : (([String:Any]?) -> Void)?
    var userInfo: [String:Any]?
    
    init(target: Any?, action: Selector?, userInfo: [String:Any]? = nil, onClick: (([String:Any]?) -> Void)?) {
        self.userInfo = userInfo
        self.onClick = onClick
        super.init(target: target, action: action)
    }
}

extension UIView {
    
    func setOnClickListener(userInfo: [String:Any], action :@escaping ([String:Any]?) -> Void){
        let tapRecogniser = ClickListener(target: self, action: #selector(onViewClicked), userInfo: userInfo, onClick: action)
        self.addGestureRecognizer(tapRecogniser)
    }
    
    @objc func onViewClicked(_ sender: ClickListener) {
        sender.onClick?(sender.userInfo)
    }
    
}
vadian
  • 274,689
  • 30
  • 353
  • 361