tee
tee

Reputation: 1336

How can I check when user's click on image in Swift 3?

I have an image displayed in on my app that I'm creating in Swift using Xcode and I want to for example print "Image Clicked" whenever the image is tapped. Here's what I got:

This is a function that is supposed to get called whenever users tap the image:

func imageViewTapped(imageView: UIImageView) {
    print("Image Clicked")
}

I have this function that adds the user interaction with the image:

func tapRecognition(image: UIImageView) {
    image.isUserInteractionEnabled = true
    let tapRecognizer = UITapGestureRecognizer(target: self, action: Selector(("imageViewTapped:")))
    image.addGestureRecognizer(tapRecognizer)
}

And last but not least I created an image of type UIImageView in my viewDidLoad() function and I called:

tapRecognition(image: imageView)

When I run this and click on the image, I get an error that states:

terminating with uncaught exception of type NSException.

Upvotes: 5

Views: 8331

Answers (2)

user7014451
user7014451

Reputation:

Here's some code that will work using Swift 3:

override func viewDidLoad() {
    super.viewDidLoad()
    
    // #1
    let myImage = UIImage(named: "myImage")
    let imageView = UIImageView()
    imageView.image = myImage

    // #2
    imageView.isUserInteractionEnabled = true
    let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
    imageView.addGestureRecognizer(tapRecognizer)

    // #3
    imageView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(imageView)
    // set imageView contraints
    
}

// #4
func imageTapped(recognizer: UITapGestureRecognizer) {
    print("Image was tapped")
    let thePoint = recognizer.location(in: view)
    let theView = recognizer.view
}
  1. While you code is a bit different and some is missing, I believe this is basically the same. Unless you need to access image or imageView elsewhere, declare them locally in viewDidLoad. (I'd suggest not naming a UIImageView as you have (image), as that can be confusing to others reading your code.)

  2. Here's where I think @rmaddy is correct. If you are using Swift 3, the correct syntax for selector is what I have. This may be the only problem with our code - and kudos for remembering that a UIImageView has a default of isUserInteractioEnabled = true.

  3. You don't have any code for the actual layout. But if you are using auto layout (as opposed to frames), the basic syntax is turn off autoresizing, make sure you add the view into the view hierarchy, and set your constraints. Many times (most even) you can set them in viewDidLoad, but if you need frame information, use either viewWillLayoutSubviews or viewDidLayoutSubviews.

  4. At first I thought your tap function was ok, but upon further inspection, it isn't. The sender isn't the view that was tapped on, it's the recognizer. I've added two lines of code after the print statement. The first puts the CGPoint of the tap into a variable. The second puts the UIView attached to the recognizer (in this case, imageView) into a variable. Also, if you need to know is a subview of imageView was in the area of the tap, use the hitTest() method.

EDIT:

I was asked how to use the view (in my example, imageView) in the function that executes on the tap.

First, the one thing you cannot do is pass it as a parameter. Both iOS and macOS doesn't work that way with their actions. But you have (at least) three options at you disposal. (None of them use the tag property, which in some scenarios is another option.)

  1. The code you've shown say you already know the the image view was tapped. This is a likely case, as every gesture you create can only be attached to a single view. If this is your scenario, consider declaring your image view at the view controller level and just code what you need inside your function.

  2. But what if you have two image views in you view controller, both with a tap gesture attached, and you want them to do something depending on which was tapped? In this case you can use the gesture's view property, with casting. The skeleton code:

    let imageView1 = UIImageView()
    let imageView2 = UIImageView()
    let tapRecognizer1 = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
    imageView1.addGestureRecognizer(tapRecognizer1)
    let tapRecognizer2 = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
    imageView2.addGestureRecognizer(tapRecognizer2)
    
    
    func imageTapped(recognizer: UITapGestureRecognizer) {
        print("Image was tapped")
        let imageView = recognizer.view as? UIImageView
        if imageView != nil {
            //do something here
        }
    }
    

    Be aware, the skeleton may have a syntax error in the casting. The logic is solid, I am writing this online, not in Xcode. Basically cast the attached UIView as an image view and check for nil.

  3. The case I usually use if if you have several subviews and you want to know which one was tapped. In your case, suppose you had two UIImageViews like above, but you have the tap recognizer attached to the main view? In this case you can use the hitTest() method. Skeleton code:

    let imageView1 = UIImageView()
    let imageView2 = UIImageView()
    let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
    imageView1.addGestureRecognizer(self.view)
    
    func imageTapped(recognizer: UITapGestureRecognizer) {
        print("Image was tapped")
        let p = recognizer.location(in: view)
        if imageView1.layer.hitTest(p) != nil {
            // do something here to imageView1
        } else if imageView2.layer.hitTest(p) != nil {
            // do something here to imageView2
        }
    }
    

    I prefer to use layers, but views have a hitTest() method also. The value to this is a view with 7 subviews can be coded with a single gesture, and if you care to ignore the tap on 3 of those subviews (and when tapped on the main view), you can.

    Regard to the last one, I've had a small example project for a while. It contains an image view with two colored labels as subviews. When either label is tapped, a counter is incremented. I've used this in 1-2 of my answers and I thought I'd put it in a GitHub repository. Feel free to look at it: Image with Labels.

Upvotes: 13

Jitendra Tanwar
Jitendra Tanwar

Reputation: 249

Change param type as follows:

func imageViewTapped(imageView:UITapGestureRecognizer? = nil ) {
    print("Image Clicked")
}

Upvotes: 0

Related Questions