1

The below Swift code manages zooming in and out of an UIImage inside a UIScrollView.

When double tapping, the image zooms into the centre and zooms out to the centre.

Question:

What code changes need to be made to set the zoom in point to be the centre of the image area the user touches on screen?

(For example, if the user double taps the top left of the image, the image would correspondingly zoom into the top left of the image.)

class ScrollViewController: UIViewController, UIScrollViewDelegate {
    var scrollView: UIScrollView!
    var imageView: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()

        imageView = UIImageView(image: UIImage(named: "image.png"))

        scrollView = UIScrollView(frame: view.bounds)
        scrollView.backgroundColor = UIColor.blackColor()
        scrollView.contentSize = imageView.bounds.size
        scrollView.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]

        scrollView.contentOffset = CGPoint(x: 1000, y: 450)

        scrollView.addSubview(imageView)
        view.addSubview(scrollView)

        scrollView.delegate = self
        setZoomScale()
        setupGestureRecognizer()
    }

    func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
        return imageView
    }

    override func viewWillLayoutSubviews() {
        setZoomScale()
    }

    func setZoomScale() {
        let imageViewSize = imageView.bounds.size
        let scrollViewSize = scrollView.bounds.size
        let widthScale = scrollViewSize.width / imageViewSize.width
        let heightScale = scrollViewSize.height / imageViewSize.height

        scrollView.minimumZoomScale = min(widthScale, heightScale)
        scrollView.zoomScale = 1.0
    }

    func scrollViewDidZoom(scrollView: UIScrollView) {
        let imageViewSize = imageView.frame.size
        let scrollViewSize = scrollView.bounds.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

        scrollView.contentInset = UIEdgeInsets(top: verticalPadding, left: horizontalPadding, bottom: verticalPadding, right: horizontalPadding)
    }

    func setupGestureRecognizer() {
        let doubleTap = UITapGestureRecognizer(target: self, action: "handleDoubleTap:")
        doubleTap.numberOfTapsRequired = 2
        scrollView.addGestureRecognizer(doubleTap)
    }

    func handleDoubleTap(recognizer: UITapGestureRecognizer) {
        if (scrollView.zoomScale > scrollView.minimumZoomScale) {
            scrollView.setZoomScale(scrollView.minimumZoomScale, animated: true)
        } else {
            scrollView.setZoomScale(scrollView.maximumZoomScale, animated: true)
        }
    }
}
user4806509
  • 2,925
  • 5
  • 37
  • 72

2 Answers2

6

According to this post. It works fine with me.

func handleDoubleTap(recognizer: UITapGestureRecognizer) {
    if (scrollView.zoomScale > scrollView.minimumZoomScale) {
        scrollView.setZoomScale(scrollView.minimumZoomScale, animated: true)
    } else {
        let touchPoint = recognizer.locationInView(view)
        let scrollViewSize = scrollView.bounds.size

        let width = scrollViewSize.width / scrollView.maximumZoomScale
        let height = scrollViewSize.height / scrollView.maximumZoomScale 
        let x = touchPoint.x - (width/2.0)
        let y = touchPoint.y - (height/2.0)

        let rect = CGRectMake(x, y, width, height)
        scrollView.zoomToRect(rect, animated: true)
    }
}
mugx
  • 9,869
  • 3
  • 43
  • 55
Willjay
  • 6,381
  • 4
  • 33
  • 58
  • Thanks @Wei Jay for posting your answer. Unfortunately this only works when the `UIScrollView` contains content that is identical to the screen size. It for example doesn't work for an `UIImageView` that is wider, taller or zoomed in on the screen. When double tapping that larger type of content, the zooming to the touched point is not correct, it zooms in to a different part of the content. I'm going to tinker, but if you have any other suggestions please update the code? Thanks. Cheers. – user4806509 Aug 23 '16 at 18:03
  • @user4806509 I am trying to figure this out too in Swift 3. Any luck? – A.J. Hernandez Oct 25 '16 at 18:05
1

Here's a Swift 2.3 function for returning the location of a touch inside a zoomable UIScrollView:

  • UIScrollView with UIImageView inside
  • Zoomed or not - scrollView.zoomScale
  • imageView.contentMode = .ScaleAspectFit
  • The image can be bigger, high res than imageView and scrollView
  • Any shape of the image, portrait, landscape or square

imageView.contentMode = .ScaleAspectFit

//import AVFoundation // needed for AVMakeRectWithAspectRatioInsideRect()

func getLocationOfTouchInImageInScrollView(paintLocation:CGPoint)->CGPoint {

    let imageSize = imageViewInScrollView.image!.size
    let imageFrame = scrollView.frame
    let imageRect = AVMakeRectWithAspectRatioInsideRect(imageSize, imageFrame)
    let imageHeightToViewHeight = max(imageSize.height, imageSize.width) / imageFrame.size.height

    let px = (max(0, min(imageSize.width, ((paintLocation.x - imageRect.origin.x) * imageHeightToViewHeight))))
    let py = (max(0, min(imageSize.height, ((paintLocation.y - imageRect.origin.y ) * imageHeightToViewHeight))))
    var imageTouchPoint = CGPointMake(CGFloat(px), CGFloat(py))

    return imageTouchPoint
}

Took me DAYS to write this one since I couldn't figure out the imageHeightToViewHeight variable, thought there was something else funny going on.

Thyselius
  • 864
  • 1
  • 10
  • 14