In my iPhone app, I need to provide the user with an ability to zoom/pan a large-ish image on the screen. This is quite simple: I use UIScrollView
, set max/min scale factors and zooming/panning works as expected. Here's where things get interesting. The image is a dynamic one, received from a server. It can have any dimensions. When the image first loads, it's scaled down (if needed) to fit completely into the UIScrollView
and is centered in the scroll view - the screenshot is below:
Because the proportions of the image are different from those of the scroll view, there's white space added above and below the image so that the image is centered. However when I start zooming the image, the actual image becomes large enough to fill the whole of the scrollview viewport, therefore white paddings at top/bottom are not needed anymore, however they remain there, as can be seen from this screenshot:
I believe this is due to the fact that the UIImageView
containing the image is automatically sized to fill the whole of UIScrollView
and when zoomed, it just grows proportionally. It has scale mode set to Aspect Fit
. UIScrollView
's delegate viewForZoomingInScrollView
simply returns the image view.
I attempted to recalculate and re-set UIScrollView
, contentSize
and image view's size in scrollViewDidEndZooming
method:
CGSize imgViewSize = imageView.frame.size;
CGSize imageSize = imageView.image.size;
CGSize realImgSize;
if(imageSize.width / imageSize.height > imgViewSize.width / imgViewSize.height) {
realImgSize = CGSizeMake(imgViewSize.width, imgViewSize.width / imageSize.width * imageSize.height);
}
else {
realImgSize = CGSizeMake(imgViewSize.height / imageSize.height * imageSize.width, imgViewSize.height);
}
scrollView.contentSize = realImgSize;
CGRect fr = CGRectMake(0, 0, 0, 0);
fr.size = realImgSize;
imageView.frame = fr;
However this was only making things worse (with bounds still being there but panning not working in the vertical direction).
Is there any way to automatically reduce that whitespace as it becomes unneeded and then increment again during zoom-in? I suspect the work will need to be done in scrollViewDidEndZooming
, but I'm not too sure what that code needs to be.
I think I got it. The solution is to use the
scrollViewDidEndZooming
method of the delegate and in that method setcontentInset
based on the size of the image. Here's what the method looks like:Note that I'm using animation on setting the inset, otherwise the image jumps inside the scrollview when the insets are added. With animation it slides to the center. I'm using
UIView
beginAnimation
andcommitAnimation
instead of animation block, because I need to have the app run on iphone 3.Here's my solution that works universally with any tab bar or navigation bar combination or w/o both, translucent or not.
Swift 5:
Why it's better than anything else I could find on SO so far:
It doesn't read or modify the UIView frame property of the image view since a zoomed image view has a transform applied to it. See here what Apple says on how to move or adjust a view size when a non identity transform is applied.
Starting iOS 7 where translucency for bars was introduced the system will auto adjust the scroll view size, scroll content insets and scroll indicators offsets. Thus you should not modify these in your code as well.
FYI: There're check boxes for toggling this behavior (which is set by default) in the Xcode interface builder. You can find it in the view controller attributes:
The full view controller's source code is published here.
Also you can download the whole Xcode project to see the scroll view constraints setup and play around with 3 different presets in the storyboard by moving the initial controller pointer to any the following paths:
Every option works correctly with the same VC implementation.
Awesome!
Thanks for the code :)
Just thought I'd add to this as I changed it slightly to improve the behaviour.
Here is an extension tested on Swift 3.1. Just create a separate *.swift file and paste the code below:
The way to use:
Here is the swift 3 version of Genk's Answer