Zoom to a point in a UIScrollView (2015 Edition)

A long, LONG time ago, I wrote a blog post on zooming to a point in UIScrollView. Even today, 3 years later, zooming to a point still isn’t a default feature of UIScrollView, and so with that functionality still being in hot demand, that original post has ended up becoming one of the most active links on my blog.

Sadly, over the years, I started receiving reports from various people using that sample code that in a lot of different scenarios, that code was actually broken. While it would work fine if the scroll view was completely zoomed out (Which was fine for iComics’ needs), if you were zoomed in at all, then the resulting zoom behavior would be incorrect. Not only that, I discovered a unique scenario in iComics where if the scroll view’s content size was of a particular shape and VERY slightly zoomed in (To a decimal fraction amount), then that code would utterly break, and the scroll view content would fly off-screen.

As a result of that catastrophic error, this year, I decided to delete that original zooming code, and rewrite the lot from scratch. Not only would this fix the broken functionality, but it also made a new feature of iComics v1.2.5, where tapping multiple times results in different zoom levels, possible.

This time, the zoom code is smart enough to realize when it’s zoomed in, and to compensate accordingly. I also incorporated iOS 7’s new animation API, so it ‘feels’ a lot smoother too than the ‘linear’ animation of the previous code.

As always, this code is in the public domain. No attribution is required, but please let me know if you do end up using it. :)

  • David Oster

    * CGPoint translatedZoomPoint = CGPointZero;
    ^ dead store, since translatedZoomPoint is initialized on the next two lines.

    * CGRectGetWidth(r) is silly since it’s actually more verbose than the obvious r.size.width, and the same for CGRectGetHeight()

    * Generally, you want to shrink or grow preserving the tapped point, so after the shrinking or growing the original tapped point is still the same. This code always brings the tapped point to the center, so with this code a series of taps, off the center, will make the contents jump around as it changes size.

    * The code looks like it might have a problem if the minimumZoomScale is so small that the entire scaled content would be smaller than the UIScrollView’s frame – normally, when showing the content smaller, you want it to fit the frame, but no smaller. This code lacks the check for that, and the adjustment to make the destRect fit the frame when it gets small.

    • * CGPoint translatedZoomPoint = CGPointZero;
      ^ dead store, since translatedZoomPoint is initialized on the next two lines.

      True, but I like being in the habit of initializing these things to 0 while I write to be on the safe side. I have been bitten by trying to use a default value of a CGPoint/CGRect before. :)

      * CGRectGetWidth(r) is silly since it’s actually more verbose than the obvious r.size.width, and the same for CGRectGetHeight()

      Autocomplete makes it slightly quicker to type. I’ve also read that there are some internal niceties of those functions that do a bit more sanity checking than accessing the raw number.

      * Generally, you want to shrink or grow preserving the tapped point, so after the shrinking or growing the original tapped point is still the same. This code always brings the tapped point to the center, so with this code a series of taps, off the center, will make the contents jump around as it changes size.

      Yeah…. bringing the tapped point into the center was the goal of this category. That’s how it works in the Photos app when you double tap on an image.

      * The code looks like it might have a problem if the minimumZoomScale is so small that the entire scaled content would be smaller than the UIScrollView’s frame – normally, when showing the content smaller, you want it to fit the frame, but no smaller. This code lacks the check for that, and the adjustment to make the destRect fit the frame when it gets small.


      Oh! Good point! I’ll have to have a play with that to see what I can do. Thanks!

      • David Oster

        Thank you. At least you know you have readers and you make them think.

        • Likewise, thanks for the comment David! :)

          Haha I think this might actually be the first time that’s actually happened. Thanks! XD

  • Max B

    Hi,
    I may have missed something, but wanted to point a simpler trick:
    Since you are animating with UIView block animation, why not do a simple [UIView animateWithDuration:0.35 animations:^{
    self.contentOffset = offset_zoom;
    ign.zoomScale = z;
    } completion:^(BOOL finished) {
    //optional if your delegate do something at that moment
    [delegate scrollViewDidEndZooming:self withView:nil atScale:z];
    }];

    • Hi Max!

      Yeah? Yeah, I was wondering that while I was writing it. There’s probably a more simple way manipulating the contentOffset and the zoomScale properties, but there’ll still be a bit of math involved. In any case, since I already had the framework of this method using zoomToRect written up, it was less time and effort for me to just improve upon the logic in that one than rethink the lot.

      But if you want to try and write a new implementation that just uses the scrollOffset and zoomScale, let me know how you go! :)

  • Pradip Vaghasiya

    Thanks for your post. It was a time saver!

    • My pleasure! Glad I could help! :)

  • Dragoș Mihai Marcean

    I am writing an app in swift. I am using an imageview inside a scrollview for zooming purposes. Let’s say that it’s a map and inside there are points of interest at certain locations. In the “scrollViewDidZoom” function I am iterating through all the subviews if the imageview and applying ——-

    btn.transform = CGAffineTransformMakeScale(1.0/scrollView.zoomScale, 1.0/scrollView.zoomScale); where btn is a subview

    I used your code to scroll to a point of interest when I tap a button ( just like a show my location on a map ) and it worked like a charm with a small modification. For making the “destinationRect”, I used the point of interest’s point and did not use the “translatedZoomPoint”

    Thank you for the help :)

    • Oh really?

      Hang on, why did you need to manually apply the transformation to the button subviews? If you added them as subviews in the image view, (or even made a container view and placed both the image view and buttons in there), then they should have scaled automatically…

      Ah well! Good to hear you got it working! Thanks a lot for that! :)