228

I'm trying to assign an UIImageView to an action when the user taps it.

I know how to create an action for a UIButton, but how could I mimic the same behavior of a UIButton, but using a UIImageView?

Jojodmo
  • 23,357
  • 13
  • 65
  • 107
user3706773
  • 2,989
  • 3
  • 15
  • 9

12 Answers12

529

You'll need a UITapGestureRecognizer. To set up use this:

override func viewDidLoad()
{
    super.viewDidLoad()

    let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageTapped(tapGestureRecognizer:)))
    imageView.isUserInteractionEnabled = true
    imageView.addGestureRecognizer(tapGestureRecognizer)
}

@objc func imageTapped(tapGestureRecognizer: UITapGestureRecognizer)
{
    let tappedImage = tapGestureRecognizer.view as! UIImageView

    // Your action
}

(You could also use a UIButton and assign an image to it, without text and than simply connect an IBAction)

Naveed Ahmad
  • 6,627
  • 2
  • 58
  • 83
Aseider
  • 5,875
  • 1
  • 14
  • 25
  • 4
    as stated below by MidHun MP, make sure you have the userInteractionEnabled = true – Sheraz Oct 02 '15 at 22:52
  • is it possible to add action for UIImageView without using UITapGestureRecognizer? – Niloy Mahmud Jan 25 '17 at 12:19
  • You could create a subclass of UIImageView and handle touches via touchesBegan: and touchesEnded:, but using a gestureRecognizer is definitely the most convenient way. – Aseider Feb 01 '17 at 22:04
  • @user3706773 you used the button as an image.but i want aspect fit for my images. then what? – Awais Fayyaz Jul 27 '18 at 12:54
  • 2
    Using Swift 4, this worked for me, but I had to add an `@objc` modifier to the function. – Mark Lyons Oct 20 '18 at 23:24
  • Nice and concise solution! Although I prefer the button also because it can handle selection states better and have a better user experience in general, while images simply don't have that by default. – Alessandro Francucci Sep 13 '19 at 10:09
  • How can I pass argument inside "imageTapped" function? – Abhishek Nov 27 '19 at 07:39
  • You can only pass another parameter for the event: `func foo(sender: Any, forEvent event: UIEvent)`. To pass other values you can create a custom UITapGestureRecognizer: `class CustomTapGestureRecognizer: UITapGestureRecognizer` and add a field that represents your data. E.g. `let tapGesture = CustomTapGestureRecognizer(target: self, action: #selector(tapSelector(sender:))) tapGesture.ourCustomValue = "someStringValue"` You can read more about it [here](https://programmingwithswift.com/pass-arguments-to-a-selector-with-swift/) – Armend Ukehaxhaj Aug 05 '20 at 11:26
  • NOTE: The view must already be in the view hierarchy, before you add gesture recognizers. Otherwise they won't work. – alekop Dec 29 '21 at 06:21
81

You need to add a a gesture recognizer (For tap use UITapGestureRecognizer, for tap and hold use UILongPressGestureRecognizer) to your UIImageView.

let tap = UITapGestureRecognizer(target: self, action: #selector(YourClass.tappedMe))
imageView.addGestureRecognizer(tap)
imageView.isUserInteractionEnabled = true

And Implement the selector method like:

@objc func tappedMe()
{
    println("Tapped on Image")
}
L.Goyal
  • 773
  • 2
  • 6
  • 23
Midhun MP
  • 103,496
  • 31
  • 153
  • 200
  • 16
    userInteractionEnabled is key. – kennydust Jul 09 '15 at 05:28
  • @hatim: What is the issue you are facing ? If you have a new issue, please post it as a new question. – Midhun MP May 06 '16 at 09:40
  • 1
    @MidhunMP I am generating the image view dynamically and have done exactly what is written in this answer but tappedMe() function is not getting called – hatim May 06 '16 at 10:49
  • 1
    It worked for me with the following changes: let tap = UITapGestureRecognizer(target: self, action: #selector(ViewController.tappedMe)) // next line -> successIndicatorImg.addGestureRecognizer(tap) // next line -> successIndicatorImg.isUserInteractionEnabled = true – Almir Campos Oct 01 '16 at 07:22
  • 2
    @hatim are you using same gesture to multiple UI views ? I just solved this issue as i was using same gesture for multiple views and only one was working for me. when i create multiple gestures it worked for me. – nadafafif Mar 16 '19 at 11:06
37

You can add a UITapGestureRecognizer to the imageView, just drag one into your Storyboard/xib, Ctrl-drag from the imageView to the gestureRecognizer, and Ctrl-drag from the gestureRecognizer to the Swift-file to make an IBAction.

You'll also need to enable user interactions on the UIImageView, as shown in this image: enter image description here

Emil
  • 7,220
  • 17
  • 76
  • 135
  • 5
    `UIImageView` doesn't respond to touches by default. You also have to enable user interactions. – rmaddy Jan 10 '15 at 20:10
  • @rmaddy Oops, that's true. Edited. – Emil Jan 10 '15 at 20:11
  • 2
    I like how you can set this up using the IB, drag/drop is powerful, and didn't expect it was possible. +1 for that. As a noob, it would be easy to not know how it's connected without "mousing around" the storyboard. It depends on your preference, but I prefer to "see it in code". – angryITguy Apr 09 '18 at 01:32
21

For Swift do this:

@IBOutlet weak var imageView: UIImageView!

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    let tap = UITapGestureRecognizer(target: self, action: #selector(ViewController.tappedMe))
    imageView.addGestureRecognizer(tap)
    imageView.isUserInteractionEnabled = true
}

func tappedMe() {
    print("Tapped on Image")
}
pkamb
  • 33,281
  • 23
  • 160
  • 191
Abhishek Kumar
  • 2,136
  • 3
  • 24
  • 36
15

Swift4 Code

Try this some new extension methods:

import UIKit

extension UIView {

    fileprivate struct AssociatedObjectKeys {
        static var tapGestureRecognizer = "MediaViewerAssociatedObjectKey_mediaViewer"
    }

    fileprivate typealias Action = (() -> Void)?


    fileprivate var tapGestureRecognizerAction: Action? {
        set {
            if let newValue = newValue {
                // Computed properties get stored as associated objects
                objc_setAssociatedObject(self, &AssociatedObjectKeys.tapGestureRecognizer, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
            }
        }
        get {
            let tapGestureRecognizerActionInstance = objc_getAssociatedObject(self, &AssociatedObjectKeys.tapGestureRecognizer) as? Action
            return tapGestureRecognizerActionInstance
        }
    }


    public func addTapGestureRecognizer(action: (() -> Void)?) {
        self.isUserInteractionEnabled = true
        self.tapGestureRecognizerAction = action
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture))
        self.addGestureRecognizer(tapGestureRecognizer)
    }


    @objc fileprivate func handleTapGesture(sender: UITapGestureRecognizer) {
        if let action = self.tapGestureRecognizerAction {
            action?()
        } else {
            print("no action")
        }
    }

}

Now whenever we want to add a UITapGestureRecognizer to a UIView or UIView subclass like UIImageView, we can do so without creating associated functions for selectors!

Usage:

 profile_ImageView.addTapGestureRecognizer {
        print("image tapped")
    }
Ravindra_Bhati
  • 1,071
  • 13
  • 28
11

You could actually just set the image of the UIButton to what you would normally put in a UIImageView. For example, where you would do:

myImageView.image = myUIImage

You could instead use:

myButton.setImage(myUIImage, forState: UIControlState.Normal)

So, here's what your code could look like:

override func viewDidLoad(){
  super.viewDidLoad()

  var myUIImage: UIImage //set the UIImage here
  myButton.setImage(myUIImage, forState: UIControlState.Normal)
}

@IBOutlet var myButton: UIButton!
@IBAction func buttonTap(sender: UIButton!){
  //handle the image tap
}

The great thing about using this method is that if you have to load the image from a database, you could set the title of the button before you set the image:

myButton.setTitle("Loading Image...", forState: UIControlState.Normal)

To tell your users that you are loading the image

Jojodmo
  • 23,357
  • 13
  • 65
  • 107
6

Need to add lazy for TapGestureRecognizer to register
since the 'self' in UITapGestureRecognizer(target: self ...) will be nil if it's not a lazy var. Even if you set isUserInteractionEnable = true, it won't register without lazy var.

lazy var imageSelector : UIImageView = {
    let image = UIImageView(image: "imageName.png")
    //now add tap gesture
    image.isUserInteractionEnabled = true
    image.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleImageSelector)))
    return image
}()
@objc private func handleImageSelector() {
    print("Pressed image selector")
}
candyline
  • 788
  • 8
  • 17
  • Is this possible to declare the function inside action: argument ? Something like this : image.addGestureRecognizer(UITapGestureRecognizer(target: self, action: { print ("ok") })) – Mathieu Rios Feb 25 '21 at 15:15
2

You can put a UIButton with a transparent background over top of the UIImageView, and listen for a tap on the button before loading the image

Jojodmo
  • 23,357
  • 13
  • 65
  • 107
depsch ali
  • 23
  • 3
  • 1
    Its a swift way of describing an option. I thought everyone knew that! – depsch ali Jul 15 '15 at 05:53
  • 3
    It is not a good idea to add unnecessary UI components without any real reason. It will increase the complexity of the UI and also you have to deal with the same auto layout constraints. It is just better to add a TapGestureRecognizer. – Gunhan Feb 06 '17 at 15:32
2
@IBOutlet weak var imageView: UIImageView!

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
//ViewController is your current view controller    
let tap = UITapGestureRecognizer(target: self, action: #selector(ViewController.tappedMe))
    imageView.addGestureRecognizer(tap)
    imageView.isUserInteractionEnabled = true
}

// Need to ass objective-c annotation 
@objc
func tappedMe() {
    print("Tapped on Image")
}
  • Your answer seems very similar to Kumar's at first glance, but there are some differences. And can you talk about the different places : ) – ziggear Feb 11 '22 at 09:03
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Feb 11 '22 at 09:03
1

Swift 4 Code


Step 1 In ViewdidLoad()

   let pictureTap = UITapGestureRecognizer(target: self, action: #selector(MyInfoTableViewController.imageTapped))
       userImageView.addGestureRecognizer(pictureTap)
       userImageView.isUserInteractionEnabled = true

Step 2 Add Following Function

@objc func imageTapped() {

        let imageView = userImageView
        let newImageView = UIImageView(image: imageView?.image)
        newImageView.frame = UIScreen.main.bounds
        newImageView.backgroundColor = UIColor.black
        newImageView.contentMode = .scaleAspectFit
        newImageView.isUserInteractionEnabled = true
        let tap = UITapGestureRecognizer(target: self, action: #selector(dismissFullscreenImage))
        newImageView.addGestureRecognizer(tap)
        self.view.addSubview(newImageView)

        self.navigationController?.isNavigationBarHidden = true
        self.tabBarController?.tabBar.isHidden = true

    }

It's Tested And Working Properly

Sachin Rasane
  • 1,501
  • 12
  • 17
0

I suggest to place invisible(opacity = 0) button on your imageview and then handle interaction on button.

levan
  • 440
  • 1
  • 8
  • 19
0

This is what I found, much easier to setup.

Here is the YouTube video

  1. Open the Library, look for "Tap Gesture Recognizer" object.
  2. Drag the object to your storyboard, and set the delegate to the image you want to trigger actions.
  3. Then go to the view controller, drag the same object to set the IBAction.
tomerpacific
  • 4,704
  • 13
  • 34
  • 52
dogteeth
  • 86
  • 5