Charlie Fish
Charlie Fish

Reputation: 20546

Creating a clickable UIImageView using an extension

I have the following Swift code.

extension UIImageView {
    func enableClickablePrint() {
        let imageTap = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
        self.addGestureRecognizer(imageTap)
        self.isUserInteractionEnabled = true
    }
    func disableClickablePrint() {
        // HERE
    }
    func toggleClickablePrint() {
        // HERE
    }

    @objc fileprivate func imageTapped(_ sender: UITapGestureRecognizer) {
        print("Image tapped")
    }
}

The problem I'm running into is how to fill out the disableClickablePrint and toggleClickablePrint functions.

I'd like to be able to do something like the following.

extension UIImageView {
    var imageTap: UITapGestureRecognizer?
    func enableClickablePrint() {
        imageTap = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
        self.addGestureRecognizer(imageTap)
        self.isUserInteractionEnabled = true
    }
    func disableClickablePrint() {
        if let theImageTap = imageTap {
            self.removeGestureRecognizer(theImageTap)
            imageTap = nil
        }
    }
    func toggleClickablePrint() {
        if let theImageTap = imageTap {
            disableClickablePrint()
        } else {
            enableClickablePrint()
        }
    }

    @objc fileprivate func imageTapped(_ sender: UITapGestureRecognizer) {
        print("Image tapped")
    }
}

But of course the problem is you can't store properties in extensions like I'm wanting to do.

Anyway to achieve this? I want to try to keep this as clean as possible, without resorting to fancy tricks unless absolutely required.

Would the correct thing to do to be to convert this into a subclass of UIImageView? I'd like to try to avoid that if possible just because if I want to turn this into a framework or library or something, subclasses don't integrate as nicely into interface builder and the developer would have to add the extra step of changing the class of all their image views. Which I think would be awesome to avoid if possible.

Upvotes: 2

Views: 855

Answers (4)

Rattanakoudom Sambath
Rattanakoudom Sambath

Reputation: 514

I've use this extension for years and it work not only image but with any view.

extension UIView {
  func addTapGuesture(target: Any, action: Selector) {
    let tap = UITapGestureRecognizer(target: target, action: action)
    tap.numberOfTapsRequired = 1
    addGestureRecognizer(tap)
    isUserInteractionEnabled = true
  }
}

Usage:

imageView.addTapGuesture(target: self, action: #selector(imageTapped))

@objc func imageTapped() {
   print("Tapped")
}

Upvotes: 0

vacawama
vacawama

Reputation: 154691

Your problem is that you need to be able to recognize the UIGestureRecognizer you added, but you can't store it in a property.

Here's a (tested) solution that subclasses UITapGestureRecognizer to make the UIGestureRecognizer identifiable and then searches self.gestureRecognizers with first(where:) to see if one has been added:

extension UIImageView {

    class _CFTapGestureRecognizer : UITapGestureRecognizer { }

    private var _imageTap: _CFTapGestureRecognizer? { return self.gestureRecognizers?.first(where: { $0 is _CFTapGestureRecognizer }) as? _CFTapGestureRecognizer }

    func enableClickablePrint() {
        // Only enable once
        if _imageTap == nil {
            let imageTap = _CFTapGestureRecognizer(target: self, action: #selector(imageTapped))
            self.addGestureRecognizer(imageTap)
            self.isUserInteractionEnabled = true
        }
    }

    func disableClickablePrint() {
        if let theImageTap = _imageTap {
            self.removeGestureRecognizer(theImageTap)
        }
    }

    func toggleClickablePrint() {
        if _imageTap == nil {
            enableClickablePrint()
        } else {
            disableClickablePrint()
        }
    }

    @objc fileprivate func imageTapped(_ sender: UITapGestureRecognizer) {
        print("Image tapped")
    }
}

Upvotes: 3

mojtaba al moussawi
mojtaba al moussawi

Reputation: 1398

Since Extensions in swift can't contain stored properties, these are the solutions:

Fisrt one is the most used workaround is to use Objective_C runtime, by using objc_getAssociatedObject and objc_setAssociatedObject functions.

OK it's a nice solution, but if there is pure swift approach to do that so why not!

In your extension, define a struct with the field that you want to use, here you want for example UITapGestureRecognizer

Tip Create this struct as private You don't need anyone to access it of course.

Then define a computed property that will use this struct....

By doing this you have achieved what you need and you don't use the Objective-C at all .

Example :

extension UIImageView {
    private struct TapGestureHelper{
        static var tapGestureRecognizer : UITapGestureRecognizer?
    }

    var imageTap: UITapGestureRecognizer?{
        get {
            return TapGestureHelper.tapGestureRecognizer
        }
        set {
            TapGestureHelper.tapGestureRecognizer = newValue
        }
    }

    func enableClickablePrint() {
        imageTap = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
        self.addGestureRecognizer(imageTap!)
        self.isUserInteractionEnabled = true
    }

    func disableClickablePrint() {
          guard let gesture = self.gestureRecognizers?.first else {
              return
          }
          self.removeGestureRecognizer(gesture)
          self.imageTap = nil
    }

    func toggleClickablePrint() {
        if let theImageTap = imageTap {
            disableClickablePrint()
        } else {
            enableClickablePrint()
        }
    }

    @objc fileprivate func imageTapped(_ sender: UITapGestureRecognizer) {
        print("Image tapped")
    }
}

Upvotes: -1

Pranav Wadhwa
Pranav Wadhwa

Reputation: 7746

You could make an extension variable using a getter/setter:

extension UIImageView {

var tap: UITapGestureRecognizer {
    get {
        return //the created tap
    }
    set(value) {
        print(value)
    }
}

}

Upvotes: -1

Related Questions