Adding a Gesture Recognizer to an Image View in Swift

Resources
Download Your Free Copy of
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Developers Learning About Swift Development

Download Your Free Copy

You are here because you want to learn how to add a gesture recognizer to a UIImageView instance. Right? In this post, I show you how to add a tap gesture recognizer to a UIImageView instance. Even though we only add a tap gesture recognizer, what you learn in this post applies to swipe gestures, pan gestures, pinch gestures, ... Remember that UIImageView is a subclass of UIView and has the same capabilities as its superclass.

Some developers ask how to add a gesture recognizer to a UIImage instance. That isn't possible. A UIImage instance isn't part of the user interface of the application. An image is commonly displayed by a UIImageView instance, which means we add a gesture recognizer to the image view (UIImageView), not the image (UIImage).

Setting Up the Project in Xcode

Fire up Xcode and create a blank project by choosing the App template from the iOS > Application section.

Setting Up the Project in Xcode

Name the project Tappable, set Interface to Storyboard, and Language to Swift.

Setting Up the Project in Xcode

Add a few images to the asset catalog of the project. You can use any image you want.

Add a few images to the asset catalog of the project.

Open ViewController.swift and declare an outlet with name imageView of type UIImageView!. We use a didSet property observer to set the image property of the image view. Using didSet property observers to configure outlets is a pattern I use a lot.

import UIKit

class ViewController: UIViewController {

    // MARK: - Properties

    @IBOutlet private var imageView: UIImageView! {
        didSet {
            imageView.image = UIImage(named: "landscape")
        }
    }

    // MARK: - View Life Cycle

    override func viewDidLoad() {
        super.viewDidLoad()
    }

}

Open Main.storyboard, add a UIImageView instance to the view controller scene, and add constraints to center the image view in the view controller's view. With the view selected, open the Connections Inspector on the right and connect the view to the outlet we defined in the ViewController class.

Creating the User Interface in Interface Builder

Build and run the application in a simulator. You should see a white view with an image view at its center.

Build and run the application in a simulator.

Adding a Tap Gesture Recognizer to an Image View

You can add a gesture recognizer in code or in Interface Builder. I always add gesture recognizers in code, but that is a personal choice. Let's start with Interface Builder.

Adding a Tap Gesture Recognizer to an Image View in Interface Builder

Open Main.storyboard and drag a tap gesture recognizer from the Object Library and drop it onto the image view we added earlier. The tap gesture recognizer appears in the Document Outline on the left.

We need to implement an action that defines what happens when the user taps the image view. Open ViewController.swift and define a method with name didTapImageView(_:). It accepts the tap gesture recognizer as its only argument.

// MARK: - Actions

@IBAction func didTapImageView(_ sender: UITapGestureRecognizer) {
    print("did tap image view", sender)
}

Open Main.storyboard and select the tap gesture recognizer in the Document Outline. Open the Connections Inspector on the right and drag from selector in the Sent Actions section to the view controller in the Document Outline. A menu pops up that displays the didTapImageView(_:) action.

Connect the Action in Interface Builder

Connect the Action in Interface Builder

Build and run the application in a simulator and tap the image view. Press Command + Shift + Y to open the console at the bottom. The console is blank, which means that the tap gesture recognizer isn't detecting any taps in the image view. Something isn't right.

The solution to this problem is easy, though. A view can only respond to user interaction if its isUserInteractionEnabled property is set to true. true is the default value of a UIView instance. For a UIImageView instance, the default value is false. That is what trips up many developers.

Revisit the didSet property observer of imageView and set its isUserInteractionEnabled property to true. We update the property in code, but you can make this change in Interface Builder if you prefer that. Build and run the application to verify that the issue is resolved and the image view receives taps.

import UIKit

class ViewController: UIViewController {

    // MARK: - Properties

    @IBOutlet private var imageView: UIImageView! {
        didSet {
            imageView.isUserInteractionEnabled = true
            imageView.image = UIImage(named: "landscape")
        }
    }

    // MARK: - View Life Cycle

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    // MARK: - Actions

    @IBAction func didTapImageView(_ sender: UITapGestureRecognizer) {
        print("did tap image view", sender)
    }

}
did tap image view <UITapGestureRecognizer: 0x133e09230; state = Ended; view = <UIImageView 0x133e06380>; target= <(action=didTapImageView:, target=<Tappable.ViewController 0x133e0a490>)>>

Adding a Tap Gesture Recognizer to an Image View in Code

Open Main.storyboard and remove the tap gesture recognizer from the image view. Navigate to the viewDidLoad() method of the ViewController class. To initialize a UITapGestureRecognizer instance, we invoke the init(target:action:) initializer. The target is the view controller and the action is the didTapViewImageView(_:) method.

import UIKit

class ViewController: UIViewController {

    // MARK: - Properties

    @IBOutlet private var imageView: UIImageView! {
        didSet {
            imageView.isUserInteractionEnabled = true
            imageView.image = UIImage(named: "landscape")
        }
    }

    // MARK: - View Life Cycle

    override func viewDidLoad() {
        super.viewDidLoad()

        // Initialize Tap Gesture Recognizer
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(_:)))
    }

    // MARK: - Actions

    @IBAction func didTapImageView(_ sender: UITapGestureRecognizer) {
        print("did tap image view", sender)
    }

}

We can remove the IBAction attribute from didTapImageView(_:) since we no longer use Interface Builder to set up the tap gesture recognizer. If we remove the IBAction attribute, the compiler throws an error.

We need to prefix the method with the objc attribute to expose it to the Objective-C runtime.

We need to prefix didTapImageView(_:) wit the objc attribute to expose it to the Objective-C runtime. This is a more advanced topic that I won't cover in this post.

// MARK: - Actions

@objc private func didTapImageView(_ sender: UITapGestureRecognizer) {
    print("did tap image view", sender)
}

In the viewDidLoad() method, we invoke addGestureRecognizer(_:) on imageView, passing in the tap gesture recognizer, to add the tap gesture recognizer to imageView.

// MARK: - View Life Cycle

override func viewDidLoad() {
    super.viewDidLoad()

    // Initialize Tap Gesture Recognizer
    let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(_:)))

    // Add Tap Gesture Recognizer
    imageView.addGestureRecognizer(tapGestureRecognizer)
}

Let's build and run the application to make sure the image view still responds to taps. Tap the image view and press Command + Shift + Y to inspect the output in the console.

Don't Forget isUserInteractionEnabled

The most common mistake developers make when working with UIImageView is not setting isUserInteractionEnabled to true. A view with isUserInteractionEnabled set to false ignores touches from the user. The touches pass through the view to the view beneath it.

Resources
Download Your Free Copy of
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Developers Learning About Swift Development

Download Your Free Copy