61

After initialisation of by subclass of UIImageView I have the following line of code:

self.userInteractionEnabled = true
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "handleTap:"))

I created the necessary associated function :

func handleTap(gestureRecognizer: UITapGestureRecognizer) {
    print("In handler")
}

On tapping on the view in question, "In handler was never printed to the console". I then removed the handler function to see if the compiler would complain about the missing function. It didn't.

I'm positively stumped. I'd truly appreciate any light people can shed on this.

Update: My class is actually a UIImageView as opposed to UIView

Kuldeep
  • 4,466
  • 8
  • 32
  • 59
Didia
  • 1,396
  • 1
  • 12
  • 18
  • 1
    Try `self.view.addGestureRecognizer` I haven't tried it in Swift, but that is what I use in Objective C. – David L Sep 25 '14 at 00:47
  • 1
    That I'm using `self.userInteractionEnabled` implies that these code snippets are actually inside the view I'm interested I modifying. As such, `self.view` is an impossibility! Thanks for contributing all the same! – Didia Sep 25 '14 at 10:24

15 Answers15

142

I was using UITapGestureRecognizer that I placed on a UILabel using Storyboard.

To get this to work I also had to place a checkmark in the block labeled: "User Interaction Enabled" in the UILabel Attributes Inspector in the Storyboard.

enter image description here

Kuldeep
  • 4,466
  • 8
  • 32
  • 59
Scooter
  • 4,068
  • 4
  • 32
  • 47
28

I discovered the answer after carefully combing through my code.

One of the parent views was created without supplying a frame:

While it's a noobish enough error to warrant deletion of this questions, odds are someone else will also have the same issue in the future...

Didia
  • 1,396
  • 1
  • 12
  • 18
  • 1
    I'm having the same problem right now. What do you mean one of the parent views was created without supplying a frame? How do you do this? – guest Apr 10 '15 at 08:04
  • I had this exact same problem. I had a view with a UIScrollView subview, which had a UIImageView subview. I'd not specified the frame for the UIImageView, and that was preventing the buttons from working. Thanks! – allocate Apr 22 '15 at 17:40
  • Does anyone know why this would cause the gesture recognizer to fail to detect the touch..? – Entalpi Mar 23 '16 at 22:17
  • I am unable to get it. Where I can add this `frame:` for UIImageView? – Faizan Mubasher Feb 27 '18 at 10:54
  • 4
    Similar to the no frame problem, I just had a UITapGestureRecognizer not work because the view's frame height was too small for the content (even though all of the content appeared in the display just fine). Might be an edge case, but adding this comment in case it helps someone. – Nate Cook Sep 06 '18 at 21:51
18

Try this

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    self.view.userInteractionEnabled = true
    var tapGesture = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
    self.view.addGestureRecognizer(tapGesture)
}

func handleTap(sender : UIView) {
    println("Tap Gesture recognized")
}
Kuldeep
  • 4,466
  • 8
  • 32
  • 59
Suresh Kumar Durairaj
  • 2,104
  • 16
  • 24
  • 1
    Thanks, adding the userInterActionEnabled to my UIImageView was the solution in my case. – Marius Waldal Jul 17 '15 at 08:51
  • 10
    It's not good to place it in viewDidAppear since you'll end up with tons of gestures added – Injectios May 01 '18 at 14:57
  • You can add those code in viewDidAppear and then remove gesture recognizers in viewDidDisappear. This will solve infinite number of gesture recognizers added to your view. – Junsu Kim Oct 20 '21 at 08:37
15

In addition to the other answers, this can be caused by adding the gesture recognizer to multiple views. Gesture recognizers are for single views only.

Reference: https://stackoverflow.com/a/5567684/6543020

bradkratky
  • 1,577
  • 1
  • 14
  • 28
10

I ran into this problem with programmatic views.

My UIView with the gesture recognizer had .isUserInteractionEnabled = true, but it did not respond to taps until I set .isUserInteractionEnabled = true for its parent views as well.

mattdedek
  • 2,525
  • 2
  • 17
  • 19
9

Most likely you add UIGestureRecognizer in wrong place. Here is working sample with UIView from storyboard. If you create your UIView dynamically then you should put this initialization in the correct constructor.

class TestView: UIView
{
    override func awakeFromNib()
    {
        self.userInteractionEnabled = true
        self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "handleTap:"))
    }

    func handleTap(gestureRecognizer: UITapGestureRecognizer)
    {
        println("Here")
    }
}
Ravi Gautam
  • 960
  • 2
  • 9
  • 20
Vitaly
  • 515
  • 3
  • 9
8

I found the solution to this problem after lot of trial and error. So there are two solution two this

1. Either add the GestureRecognizer in viewDidLoad() and turn userInteractionEnabled = true

2. If using computed property use lazy var instead of let to the property.

lazy var profileImageView: UIImageView = {
    let iv = UIImageView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
    iv.image = #imageLiteral(resourceName: "gameofthrones_splash")
    iv.contentMode = .scaleAspectFill
    iv.translatesAutoresizingMaskIntoConstraints = false
    iv.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleSelectProfileImageView)))
    iv.isUserInteractionEnabled = true
    return iv
}()
Kuldeep
  • 4,466
  • 8
  • 32
  • 59
Mayank Wadhwa
  • 1,673
  • 17
  • 20
5

For anyone that is still having problems even with all the previous answers, make sure you are using UITapGestureRecognizer instead of UIGestureRecognizer, I kept missing this detail while trying to find what was wrong.

JalxP
  • 329
  • 3
  • 10
2

This Code works for me with XCode 7.0.1

import UIKit

class ImageView: UIImageView {

    init(frame: CGRect, sender: Bool, myImage: UIImage) {
        super.init(frame: frame)
        self.image = myImage
        initBorderStyle(sender)

        // enable user interaction on image.
        self.userInteractionEnabled = true
        let gesture = UITapGestureRecognizer(target: self, action: "previewImage:")
        addGestureRecognizer(gesture)

    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    func previewImage(myGesture: UITapGestureRecognizer? = nil) {
        print("i'm clicked")
    }

    private func initBorderStyle(sender: Bool) {
        self.layer.masksToBounds = true
        self.layer.cornerRadius = 8
        self.layer.borderWidth = 0.5
        self.layer.borderColor = getBorderColor(sender)
        self.backgroundColor = getColor(sender)
    }


    func getBorderColor(sender: Bool) -> CGColor {
        var result: CGColor
        if sender {
            result = UIColor(red: 0.374, green: 0.78125, blue: 0.0234375, alpha: 0.5).CGColor
        } else {
            result = UIColor(red: 0.3125, green: 0.6015625, blue: 0.828125, alpha: 0.5).CGColor
        }
        return result
    }
}
Kuldeep
  • 4,466
  • 8
  • 32
  • 59
Tarek
  • 21
  • 1
2

Xcode 11.4 Swift 5.2

UserInteraction is enabled by default on Custom UIView

import UIKit

class YourView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }

    func commonInit() {
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapped))
        self.addGestureRecognizer(tapGesture)
    }

    @objc func tapped() {
        // do something
    }
}

If you want to be notified elsewhere that the custom UIView has been tapped, then it is convenient to use a Notification.

It is best to create this as an extension to prevent it being Stringly typed.

extension Notification.Name {
    static let DidTapMyView = Notification.Name("DidTapMyView")
}

In your custom UIView when the tapped function is called

@objc func tapped() {
    NotificationCenter.default.post(name: .DidTapMyView, object: self)
}

In your UIViewController where you want to listen for the Notification:

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(myViewWasTapped), name: .DidTapMyView, object: nil)
}

@objc func myViewWasTapped() {
    // Notified here - do something
}
rbaldwin
  • 4,581
  • 27
  • 38
2

Here's a list of my findings in 2021, XCode 13:

  • Make sure both your view and all of its superviews have set width & height constraints(this one is crucial)
  • Set isUserInteractionEnabled = true for your view
  • There's no need to set explicit frame or isUserInteractionEnabled for other super views
Adam Bardon
  • 3,829
  • 7
  • 38
  • 73
1

It turned out that my gesture didn't work because it was in a normal class, and not a subclass of UIView or anything.

I solved the issue by creating a subclass of UIView that I had an instance of in this normal class and placed the gesture logic in that.

George
  • 25,988
  • 10
  • 79
  • 133
1

In addition the above (userInteractionEnabled, clipsToBounds, etc.), if you are working with a view in a child view controller be sure that you have added it as a child with myParentViewController.addChild(myChildViewController) — we've run into a couple of situations now where visible views were not firing recognizing gestures because their view controllers hadn't been added, and presumably the VCs themselves were not being retained.

Jeff
  • 4,751
  • 5
  • 31
  • 35
  • Thank you for posting this, I had the same issue and was having a hard time debugging. This helped me look at the right place! – vb11 Aug 22 '20 at 09:18
1

I solved my issue by setting the height to the UIView.

optionView.heightAnchor.constraint(equalToConstant: 18).isActive = true
-1

I had the same issue in my programmatically created ViewController. The bug was fixed when I added a backgroundColor to the view.

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemBackground
        
        let tap = UITapGestureRecognizer(target: self.view, action: #selector(UIView.endEditing))

        view.addGestureRecognizer(tap)

    }
}
Lenny
  • 23
  • 6
  • I wish people would comment when they downvote. As weird as it seems this worked for me. See https://stackoverflow.com/questions/6010186/uitapgesturerecognizer-on-transparent-view – Simon Pickup Sep 12 '21 at 14:58