eifersucht
eifersucht

Reputation: 691

How to add tap functionality to label using UITapGestureRecognizer in static func

I need to add tap functionality to labels (to open a website) in my app dynamically, I was thinking in create static function inside a class. I want to launch the function in any ViewController of my app.

I´ve done this using Swift 3:

class Launcher {
    static func openWeb(label: UILabel, url: String) {
        func tapFunction(sender:UITapGestureRecognizer) {

            if #available(iOS 10.0, *) {
                UIApplication.shared.open(URL(string: url)!, options: [:], completionHandler: nil)
            } else {
                UIApplication.shared.openURL(URL(string: url)!)
            }
        }

        let tap = UITapGestureRecognizer(target: self, action: #selector(tapFunction))
        label.isUserInteractionEnabled = true
        label.addGestureRecognizer(tap)

        if #available(iOS 10.0, *) {
            UIApplication.shared.open(URL(string: url)!, options: [:], completionHandler: nil)
        } else {
            UIApplication.shared.openURL(URL(string: url)!)
        }

    }
}

But doesn't work because I'm getting error in action: #selector(tapFunction)

error: Argument of '#selector' cannot refer to local function 'tapFunction(sender:)'

If I use this inside a ViewController code using action: #selector(myView.tapFunction) like the following, works

Inside viewDidLoad

let tap = UITapGestureRecognizer(target: self, action: #selector(MyViewController.tapFunction))
mylabel.isUserInteractionEnabled = true
mylabel.addGestureRecognizer(tap)

Separated function inside ViewController

func tapFunction(sender:UITapGestureRecognizer) {

        if #available(iOS 10.0, *) {
            UIApplication.shared.open(URL(string: url)!, options: [:], completionHandler: nil)
        } else {
            UIApplication.shared.openURL(URL(string: url)!)
        }
    }

I want to convert this last code to static function inside my Class to call it in any ViewController. Thank you

Upvotes: 0

Views: 403

Answers (3)

GetSwifty
GetSwifty

Reputation: 7746

First, there are some general problems with your approach. Selectors work by message passing. E.g. with UITapGestureRecognizer(target:, action:) The message calling the method (the action) is sent to the variable ( the target).

When you create a local function, that function is a member of the enclosing function and not the containing class or instance, so your approach categorically cannot work.

Even if it could work, it would also go against OOP and MVC design principals. A Label should not be in charge of what happens when it's tapped, just as the title of a book is not in charge of opening the book.

Taking all that into consideration, this is how I would solve your problem:

extension UIViewController {

    func addTapGesture(to view: UIView, with selector: Selector) {
        view.isUserInteractionEnabled = true
        view.addGestureRecognizer(UITapGestureRecognizer(target: view, action: selector))
    }
}

extension UIApplication {

    func open(urlString: String) {

        if #available(iOS 10.0, *) {
            open(URL(string: urlString)!, options: [:], completionHandler: nil)
        } else {
            openURL(URL(string: urlString)!)
        }
    }
}

class YourVC: UIViewController {

    var aLabel: UILabel? = nil

    func addTapGesture() {
        addTapGesture(to: aLabel!, with: #selector(tapGestureActivated))
    }

    func tapGestureActivated(gesture: UITapGestureRecognizer?) {
        UIApplication.shared.open(urlString: "YourURLHere")
    }
}

This abstracts away the boilerplate and is simple at the point of use, while still properly separating out Type responsibilities and using generalized functionality that can be re-used elsewhere.

Upvotes: 1

matt
matt

Reputation: 534958

A method that is supposed to communicate with an instance cannot be a static / class method. That's what static / class method means; it is about the class — there is no instance in the story. Thus what you are proposing is impossible, unless you hand the static method the instance it is supposed to talk to.

To me personally, a tappable URL-opening label sounds like a label subclass, and this functionality would then be an instance method of that label.

Upvotes: 2

LLIAJLbHOu
LLIAJLbHOu

Reputation: 1313

Try resolve it using extension

extension UILabel {
    func openWeb(url: String) {
        let tap = UITapGestureRecognizer(target: self, action: #selector(tapFunction))
        self.isUserInteractionEnabled = true
        self.addGestureRecognizer(tap)

        openURL(url: url)
    }

    func tapFunction(sender:UITapGestureRecognizer) {
        openURL(url: "")
    }

    func openURL(url: String) {
        if #available(iOS 10.0, *) {
            UIApplication.shared.open(URL(string: url)!, options: [:], completionHandler: nil)
        } else {
            UIApplication.shared.openURL(URL(string: url)!)
        }
    }
}

let label = UILabel()
label.openWeb(url: "123")

To store link into label you can use associations with label object.

Upvotes: 2

Related Questions