In this tutorial I will explain how to embed a UIImageView
in a UIScrollView
to allow zooming and panning of images, similar to Apple’s Photo app. I’ll dig a little bit deeper than most tutorials on the web and explain how to work around some of UIScrollView's
odd behavior.
Getting Started
So much time has passed since I wrote first part of this tutorial that some changes need to be made to the completed source code from that step before continuing. Specifically, I have decided to use Automatic Reference Counting (ARC) to simplify memory management. I’ll talk more about ARC in an upcoming tutorial. So to get started download the update source code AllAbountImages2ARC_StartingPoint
When you open the project you will also notice that I have provided a ready-made .xib
file for the SecondViewController
class. This .xib
consists of a UIScrollView
that contains a UIImageView
. These views are already connected to the scrollView
and imageView
IBOutlet
s in the SecondViewController
class. The project should compile and run for you as downloaded, but when you press the tab for the second view controller all you get is a white screen.
Panning in a UIScrollView
To enable panning in a UIScrollView
all you need to do is tell the scroll view the size of the content and, of course, give it something more interesting that an empty UIImageView
to pan! To do this, open the file SecondViewController.m
and modify your viewDidLoad
method as shown below.
- (void)viewDidLoad { [super viewDidLoad]; UIImage* image = [UIImage imageNamed:@"KinkakuJi"]; NSAssert(image, @"image must not be nil." "Check that you added the image to your bundle and that " "the filename above matches the name of your image."); self.imageView.image = image; [self.imageView sizeToFit]; self.scrollView.contentSize = image.size; }
If all goes well, you should be able to run your project, hit the “Second” tab and be presented with an image of Kinkaku-ji that you can pan. As you pan the UIScrollView
will display scroll indicators as shown in Figure 1.
Try commenting out line 14 in the code above. Notice that you will not be able to pan the image. So if you are using a UIScrollView
and it refuses to pan, it probably means you forgot to set the contentSize
property!
So is that all there is to panning? Unfortunately, not quite, as we will see later in the tutorial under certain circumstances the default behavior of UIScrollView
is probably not what we expect. More about that later.
Zooming in a UIScrollView
To get zooming to work in a UIScrollView
there are a few requirements:
- the
maximumScale
property must be set, - the
delegate
property must be set, and - the delegate must implement the
viewForZoomingInScrollView:
selector.
Open the SecondViewController.h
file and add a UIScollViewDelegate
protocol specification to the interface
line as shown.
@interface SecondViewController : UIViewController
This line tells the compiler that you are going to implement some of the UIScrollViewDelegate
methods and make your view controller eligible to be assigned to the delegate
property of a UIScrollView.
Next, open the file SecondViewController.m
and the following method at the end of the file.
- (UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView { return self.imageView; }
When the scroll view detects a pinch gesture for zooming it calls this method to get a suitable view to use for zooming. In our case, this is straightforward, we just use our image view but if, for example, our content had complex vector graphics that took some time to redraw we might return a special simplified view from this method.
Finally, modify your viewDidLoad
method by adding the highlighted lines.
self.scrollView.contentSize = image.size; self.scrollView.delegate = self; self.scrollView.minimumZoomScale = 1.0; self.scrollView.maximumZoomScale = 100.0;
Now if you compile and run your project you should be able to both zoom and pan. If you are working on the simulator you can use option-drag to simulate pinch gestures.
Working Around UIScrollView's
Strange Behavior
When you run the tutorial, if you look very carefully, you may notice that the photo is not exactly vertically centered in the UIScrollView
. Since the example image is almost the same height as the scroll view this may be hard to see. To make it more obvious change the line that loads the image to load the smaller GoldenGate.png
image.
UIImage* image = [UIImage imageNamed:@"GoldenGate"];
Now rerun the tutorial. The image will appear somewhere in the top right of the scroll view and zooming will behave strangely. To be quite honest, I am not sure how Apple intended this to behave, but it certainly seems odd. The real issue seems to be that UIScrollView
does not like the frame origins of any of its subviews to be negative or for the contentOffset
to be negative. To work around this you will add a method to place a UIView
by center in a UIScrollView
.
First add the method definition to SecondViewController.h
:
@interface SecondViewController : UIViewController<UIScrollViewDelegate> @property (nonatomic) IBOutlet UIScrollView* scrollView; @property (nonatomic) IBOutlet UIImageView* imageView; - (void)view:(UIView*)view setCenter:(CGPoint)centerPoint; @end
Now add the method implementation to SecondViewController.m
- (void)view:(UIView*)view setCenter:(CGPoint)centerPoint { CGRect vf = view.frame; CGPoint co = self.scrollView.contentOffset; CGFloat x = centerPoint.x - vf.size.width / 2.0; CGFloat y = centerPoint.y - vf.size.height / 2.0; if(x < 0) { co.x = -x; vf.origin.x = 0.0; } else { vf.origin.x = x; } if(y < 0) { co.y = -y; vf.origin.y = 0.0; } else { vf.origin.y = y; } view.frame = vf; self.scrollView.contentOffset = co; }
Let’s take a closer look at what this method does. Lines 3 and 4 make local copies of the view’s frame
and the scroll view’s contentOffset
. Lines 6 and 7 calculate the x
and y
coordinates of the view’s origin from the centerPoint
parameter. If the calculated x
is negative then in lines 11 and 12 the x coordinate of our copy of the view’s frame origin is set to 0 and we set our copy of the scroll view’s contentOffset
to shift the view appropriately. If the calculated value is positive we can use it as the frame origin. Lines 18-26 carry out the same logic for the y
coordinate. Finally, lines 28 and 29 set the modified values in the view and scroll view.
To use this code override viewDidAppear:
in SecondViewController.m
- (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; CGPoint centerPoint = CGPointMake(CGRectGetMidX(self.scrollView.bounds), CGRectGetMidY(self.scrollView.bounds)); [self view:self.imageView setCenter:centerPoint]; }
Now compile and run the project and the small Golden Gate image should appear in the center of view.
So is that it? Unfortunately, no! Try zooming the image and notice that it behaves strangely.
It turns out that, left to its own devices, a UIScrollView
will (sometimes) set the frame origin of the zoom view to a negative value and confuse itself! The way to work around this is to use the scrollViewDidZoom:
delegate method to ensure this does not happen.
Add the following code to SecondViewController.m
:
- (void)scrollViewDidZoom:(UIScrollView *)sv { UIView* zoomView = [sv.delegate viewForZoomingInScrollView:sv]; CGRect zvf = zoomView.frame; if(zvf.size.width < sv.bounds.size.width) { zvf.origin.x = (sv.bounds.size.width - zvf.size.width) / 2.0; } else { zvf.origin.x = 0.0; } if(zvf.size.height < sv.bounds.size.height) { zvf.origin.y = (sv.bounds.size.height - zvf.size.height) / 2.0; } else { zvf.origin.y = 0.0; } zoomView.frame = zvf; }
Let’s examine this code more carefully. Lines 3 and 4 get the zoom view and a copy of its frame. In lines 5-12, if the zoom view, at the current zoom level, is narrower than the scroll view then the frame origin is set to ensure the zoom view is centered horizontally within the scroll view otherwise the origin is set to zero. Lines 13-20 carry out the same logic in the vertical direction.
If you now compile and run your project the zooming and panning behavior should correct.
You can download the completed code for this tutorial here: AllAbountImages2ARC_EndPoint
This site is ad supported. Please consider visiting our sponsors while downloading.
Pingback: Subby
Thank you so much for this great tutorial.
I’d like to use multiple images by using this codes.
However, imageNamed code is letting me only use on image file even though I tried to apply the view controller to several other images.
How can I use this view to another images in one project?
I’m using storyboard, and I’m trying to make 5 views that has this zoom funtion using by the same view controller. Please advise me. Thanks!