iOS Pinch To Zoom Swift Tutorial

I wanted to add a pinch to zoom feature to my side project and could not find an updated tutorial for how to replicate the iOS single image view. This is the view you see after tapping a photo in the iOS Photos app. Here are the steps I took to recreate this experience.

To get started, add a new View Controller to your Storyboard and create a new View Controller class. After configuring the controller’s class, add a UIScrollView to the View Controller and pin its edges to the superview’s edges at 0. You can adjust the pin reference by clicking on the constraint and adjusting the Second Item dropdown to Superview.

UIScrollView Constraint

To ensure the navigation bar and action bar safe areas do not interfere with the image centering, set the UIScrollView Content Insets to Never. For aesthetics I also turned off Show Horizontal Indicator and Show Vertical Indicator.

UIScrollView Content Inset

Add a UIImageView as a child to the UIScrollView and pin its edges to the UIScrollView at 0. This will allow the UIScrollView to know how large the image is for scrolling. Set User Interaction Enabled too true for the UIImageView.

With the storyboard view setup, we need to set the UIScrollView delegate and create outlets for the UIScrollView and UIImageView. Set the UIScrollView delegate to the View Controller’s class and add the outlets for each view. For the purpose of this tutorial we will assume variable “zoomImage” has as a UIImage to work with. In the “viewDidLoad” override set the “imageView” image as “zoomImage.”

@IBOutlet weak var imageScrollView: UIScrollView!
@IBOutlet weak var imageView: UIImageView!

var zoomImage = UIImage(named: "CanyonFilter.jpg")

override func viewDidLoad() {
    super.viewDidLoad()
    imageView.image = zoomImage
}

At the bottom of the class file, add an extension for helper methods and create a new method for calculating and setting the UIScrollView’s minimum zoom scale. This method will scale the image down so it fits proportionally in the screen and set the UIScrollView’s minimum scale. You will notice we are referencing the view’s dimensions and not the UIScrollView’s dimensions. This is due to the UIScrollView reporting larger than view dimensions on initial load. If anyone knows why this happens, please leave a comment below.

private func setMinZoomScaleForImageSize(_ imageSize: CGSize) {
    let widthScale = view.frame.width / imageSize.width
    let heightScale = view.frame.height / imageSize.height
    let minScale = min(widthScale, heightScale)
    
    // Scale the image down to fit in the view
    imageScrollView.minimumZoomScale = minScale
    imageScrollView.zoomScale = minScale
    
    // Set the image frame size after scaling down
    let imageWidth = imageSize.width * minScale
    let imageHeight = imageSize.height * minScale
    let newImageFrame = CGRect(x: 0, y: 0, width: imageWidth, height: imageHeight)
    imageView.frame = newImageFrame
    
    centerImage()
}

Now that we have scaled the image down, we need to center it in the view. Add another method to your helper methods extension for calculating and setting the UIScrollView’s offset.

private func centerImage() {
    let imageViewSize = imageView.frame.size
    let scrollViewSize = view.frame.size
    let verticalPadding = imageViewSize.height < scrollViewSize.height ? (scrollViewSize.height - imageViewSize.height) / 2 : 0
    let horizontalPadding = imageViewSize.width < scrollViewSize.width ? (scrollViewSize.width - imageViewSize.width) / 2 : 0
    
    imageScrollView.contentInset = UIEdgeInsets(top: verticalPadding, left: horizontalPadding, bottom: verticalPadding, right: horizontalPadding)
}

To scale and center the image before the View Controller comes into view, we need to override viewWillLayoutSubviews in the original class declaration. This override is also called when the phone is rotated. We pass the image dimensions into the method for calculating the minimum image scale and new image size.

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    
    if let zoomImage = zoomImage {
        setMinZoomScaleForImageSize(zoomImage.size)
    }
}

Add another extension that conforms to UIScrollViewDelegate. In this extension add the following methods. The former viewForZooming is required for the UIScrollView to know which view its manipulating. The latter scrollViewDidZoom will be called each time the user pinches to zoom. This method will keep the image centered while zooming.

extension ImageZoomViewController: UIScrollViewDelegate {
    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return imageView
    }
    
    func scrollViewDidZoom(_ scrollView: UIScrollView) {
        centerImage()
    }
}

Running the application now, you should be able to pinch to zoom on the image and have it scale.

Add Double Tap To Zoom In/Out

In the iOS Photos app, you can double tap the image and have it zoom all the way in or out. To add this feature, add a Tap Gesture Recognizer to the UIImageView In the Storyboard, set the gesture recognizer’s number of taps to 2.


In the View Controller’s class add an action for the Tap Gesture Recognizer with the sender as UITapGestureRecognizer. In this action we will determine if the image should scale in or out on double tap. On the zoom in, we are zooming the image at the point where the user double tapped.

@IBAction func doubleTapImage(_ sender: UITapGestureRecognizer) {
    if imageScrollView.zoomScale == imageScrollView.minimumZoomScale {
        imageScrollView.zoom(to: zoomRectangle(scale: imageScrollView.maximumZoomScale, center: sender.location(in: sender.view)), animated: true)
    } else {
        imageScrollView.setZoomScale(imageScrollView.minimumZoomScale, animated: true)
    }
}

We have a new helper method to calculate and return the frame to zoom too based on the tap point. This method calculates the projected image frame size and center point, compensating for the image scale factor.

private func zoomRectangle(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
        zoomRect.origin.x = center.x - (center.x * imageScrollView.zoomScale)
        zoomRect.origin.y = center.y - (center.y * imageScrollView.zoomScale)
        
        return zoomRect
    }

Run the app again and double tap a point on the picture, the UIScrollView should zoom in on that point.

Leave a Reply

Your email address will not be published. Required fields are marked *