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.
First of all, the selector
of the target/action pattern has two fixed forms:
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)
}
}