A UIImageView is frequently used for displaying all types of images in iOS apps, but unfortunately, image views don't support any type of zooming, such as pinch to zoom. In this tutorial, we'll see how it's possible to use a UIScrollView to make any image view support pinch to zoom.

As we are using a UIScrollView, the image can also be moved around after zooming, so it feels natural. After adding support for pinch to zoom, we will also add support for double tap to zoom.

If you'd like to see the final version of this tutorial, feel free to jump to the Conclusion, where you'll find a Swift Playground demonstrating everything in this tutorial.

Pinch to Zoom

First, create a new class named ImageZoomView, which we'll use as the container view for the UIImageView. For now, we'll only add an image view and set it up in our custom init method.

We'll also call some functions that we haven't created yet so we don't need to come back and add them later.

class ImageZoomView: UIScrollView {
    var imageView: UIImageView!

    convenience init(frame: CGRect, image: UIImage) {
        self.init(frame: frame)

        // Creates the image view and adds it as a subview to the scroll view
        imageView = UIImageView(image: image)
        imageView.frame = frame
        imageView.contentMode = .scaleAspectFill
        addSubview(imageView)

        setupScrollView()
        setupGestureRecognizer()
    }
}

However, this container won't work yet, as the scroll view doesn't know what view it should use for the scroll content.

Next, we'll make our class conform to the UIScrollViewDelegate, and create a new method where we can set up the scroll view.

class ImageZoomView: UIScrollView, UIScrollViewDelegate {
    // Sets the scroll view delegate and zoom scale limits.
    // Change the `maximumZoomScale` to allow zooming more than 2x.
    func setupScrollView() {
        delegate = self
    }
}

As we are already calling this method in the init we created earlier, we can now just add support for the delegate's viewForZooming method to tell the delegate what view to use.

// Tell the scroll view delegate which view to use for zooming and scrolling
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
    return imageView
}

Our ImageZoomView will now work as expected and display the image in the image view.

To finish off pinch to zoom support, you can optionally set the minimum and maximum zoom scales to use, so the user can't zoom in or out more than a certain amount. Just add these lines to the setupScrollView method we created earlier.

minimumZoomScale = 1.0
maximumZoomScale = 2.0

Double Tap to Zoom

Now that pinch to zoom is done, we can add support for double tap to zoom, which lets users double tap the screen to automatically zoom in to the location they tapped.

To do this, we'll need to add a new gesture recognizer right after our image view variable from earlier, at the top of our ImageZoomView class.

var gestureRecognizer: UITapGestureRecognizer!

Then, we'll create a new function that sets up the gesture recognizer, which we are already calling in our init code from earlier.

// Sets up the gesture recognizer that receives double taps to auto-zoom
func setupGestureRecognizer() {
    gestureRecognizer = UITapGestureRecognizer(target: nil, action: nil)
    gestureRecognizer.numberOfTapsRequired = 2
    addGestureRecognizer(gestureRecognizer)
}

However, this won't work yet, because while the gesture recognizer is now detecting double taps, we haven't told it what to do yet!

Replace the first line of the setupGestureRecognizer() function with the below line, which calls a handleDoubleTap() function that we'll set up next to handle the taps:

gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap))

Now that our gesture recognizer is calling our new function when it detects double taps on the screen, we need to actually define the function.

In our handling function, we'll check whether the current zoom level (zoomScale) is 1 (the image is fully zoomed out) or not. If the image is already zoomed, we'll zoom out to the normal zoom level, but if the image hasn't been zoomed, we'll ask the UIScrollView to zoom to the user's finger, which we'll calculate later on.

// Handles a double tap by either resetting the zoom or zooming to where was tapped
@objc func handleDoubleTap() {
    if zoomScale == 1 {
        zoom(to: zoomRectForScale(maximumZoomScale, center: gestureRecognizer.location(in: gestureRecognizer.view)), animated: true)
    } else {
        setZoomScale(1, animated: true)
    }
}

Our double taps are now being handled, so the last thing we need to do is create the function to calculate where to zoom to when the user double taps the screen. It wouldn't be a nice experience if the image was zoomed to a random location, so we'll use the touch point on the screen to determine where to zoom to.

In our handleDoubleTap method, we're passing in the center of the user's touch and the maximumZoomScale from the UIScrollView, so we can use that to calculate the rectangle, or 'cutout', of the image to zoom to:

// Calculates the zoom rectangle for the scale
func zoomRectForScale(_ scale: CGFloat, center: CGPoint) -> CGRect {
    var zoomRect = CGRect.zero
    zoomRect.size.height = imageView.frame.size.height / scale
    zoomRect.size.width = imageView.frame.size.width / scale
    let newCenter = convert(center, from: imageView)
    zoomRect.origin.x = newCenter.x - (zoomRect.size.width / 2.0)
    zoomRect.origin.y = newCenter.y - (zoomRect.size.height / 2.0)
    return zoomRect
}

Conclusion

As we've seen, while UIImageView doesn't natively support zooming, it's quite easy to add support using a container UIScrollView, and using a UITapGestureRecognizer we were able to additionally support double tap to zoom, a frequently used interaction method on iOS.

I've made a Swift Playground which has all the code from this tutorial. To make it easier to run the playground, I've included some extra code in the ImageZoomView's init method which loads an image I bundled with the playground.

Download Materials

I hope you learned something new from this tutorial! If you have any questions or feedback, please feel free to send it to [email protected]! Thanks for reading 🙂