UIScrollViews are one of the most useful controls in iOS. They are a
great way to present content larger than a single screen, and there’s a
lot of tips and tricks about using them you should know!
In this tutorial you’ll learn all about UIScrollViews, from beginning to advanced. You’ll learn:
This tutorial also assumes that you know how to use Interface Builder to add new objects to a view and connect outlets, so make sure that you do. Further, this tutorial uses a storyboard. You’ll want to get familiar with them if you aren’t already, perhaps by reading the Storyboards tutorial on this site.
Lastly, I’ll be using Xcode 4 in this tutorial, so make sure you’re fully updated to the latest version available through the Mac App Store.
To build the tableview menu, do the following:
Now let’s look at those methods that I said you’d implement – centerScrollViewContents, scrollViewDoubleTapped: and scrollViewTwoFingerTapped:. Add the following code above viewDidLoad:
The point of this method is to get around a slight annoyance with UIScrollView,
which is: if the scroll view content size is smaller than its bounds,
then it sits at the top-left rather than in the center. Since you’ll be
allowing the user to zoom out fully, you’d rather like the image to sit
in the center of the view, wouldn’t you? :] This method accomplishes
that by positioning the image view such that it is always in the center
of the scroll view’s bounds.
Next up is scrollViewDoubleTapped:. Add this just above viewDidLoad again:
This method is called when the tap gesture recognizer fires.
Remember, you set that up to recognize double-tap events. Here’s a
step-by-step guide to what scrollViewDoubleTapped: does:
This is similar to the way you zoomed in, except you don’t bother
calculating anything about where the user tapped in the view, because it
doesn’t particularly matter. Trust me, it’ll look good if you just do
it like this. :]
Now, remember how you set up ViewController as a UIScrollView delegate? Well, now you’re going to implement a couple of needed methods for a UIScrollView delegate. The first is viewForZoomingInScrollView. Add the following above viewDidLoad:
This is the heart and soul of the scroll view’s zooming mechanism.
You’re telling it which view should be made bigger and smaller when the
scroll view is pinched. So, you tell it that it’s your imageView.
The second method you’ll need to implement is scrollViewDidZoom:, which is a notification when the scroll view has been zoomed. Here you need to re-center the view – if you don’t, the scroll view won’t appear to zoom naturally, instead, it will sort of stick to the top-left. Add this above viewDidLoad:
Now take a deep breath, give yourself a pat on the back and build and
run your project. Tap on Image scroll and if everything went smoothly,
you’ll end up with a lovely image that you can zoom, pan and tap.
Create a new file with the iOS\Cocoa Touch\UIViewController subclass template. Name the class CustomScrollViewController and make sure that Targeted for iPad and With XIB for user interface are not checked. Click Next and save it with the rest of the project.
Open CustomScrollViewController.h and replace the contents with this:
Next, go to MainStoryboard.storyboard and just as before,
add a view controller that’s wired up with a push segue from the 2nd row
of the table. This time, set the view controller’s class to be the
class just created, CustomScrollViewController.
Also add a scroll view and connect it to the outlet created and set the view controller as its delegate, just as before.
Then, open CustomScrollViewController.m and set up the class continuation category at the top (above the @implementation line) like this:
And add the property synthesizers below the @implementation line:
You’ll probably notice the lack of gesture recognizer callbacks. That
is simply to make this part of the tutorial more straightforward. Feel
free to add them in afterwards as an additional exercise.
The only other difference compared to the previous view controller is that instead of a UIImageView, we’ve got a UIView and it’s called containerView. That should be a little hint as to how this is all going to work.
Now, implement viewDidLoad and viewWillAppear: like so.
You might be feeling a sense of deja-vu here, as it’s very familiar code. In fact, viewWillAppear:
is almost identical to the previous code, except for the line where you
set the zoomScale. Here, we set the zoomScale to 1 instead of minScale
so that we’d have the content view at normal size instead of it fitting
the screen. Since we aren’t going to implement the zoom handlers, if the
view fits the screen, you will not be able to test the scroll view by
panning the view around.
viewDidLoad, however, sets up a view hierarchy with a single root view, which is your instance variable, containerView. Then that single view is added to the scroll view. That is the key here – just one view can be added to the scroll view if you’re going to be zooming in, because as you’ll recall, you can only return one view in the delegate callback, viewForZoomingInScrollView:.
Again, implement centerScrollViewContents and the two UIScrollView delegate methods, substituting imageView with containerView from the original versions. (You can add the code above viewDidLoad, as before.)
Now build and run your project. This time, select Custom view scroll
and watch in amazement as you can pan around a beautifully hand-crafted
scene. (If you add in the gesture recognizers from the previous code,
you’ll even be able to zoom in and out.
Read rest of entry
In this tutorial you’ll learn all about UIScrollViews, from beginning to advanced. You’ll learn:
- How to use a scroll view to view a very large image.
- How to keep the scroll view’s content centered while zooming.
- How to embed a complex view hierarchy inside a UIScrollView.
- How to use UIScrollView’s paging feature in conjunction with the UIPageControl, to allow scrolling through multiple pages of content.
- How to make a “peeking” scroll view that gives a glimpse of the previous/next page as well as the current page.
- And much more!
This tutorial also assumes that you know how to use Interface Builder to add new objects to a view and connect outlets, so make sure that you do. Further, this tutorial uses a storyboard. You’ll want to get familiar with them if you aren’t already, perhaps by reading the Storyboards tutorial on this site.
Lastly, I’ll be using Xcode 4 in this tutorial, so make sure you’re fully updated to the latest version available through the Mac App Store.
Getting Started
Fire up Xcode and create a new project with the iOS\Application\Single View Application template. Enter ScrollViews for the product name, enter the company identifier you used when creating your App ID, leave the class prefix blank, set device family to iPhone, and make sure that Use Storyboards and Use Automatic Reference Counting are checked (but leave the other checkboxes.To build the tableview menu, do the following:
- Open MainStoryboard.storyboard and delete the scene that’s already in there by selecting the view controller (click on it on the story board) and then deleting it.
- Then, add a table view controller by dragging one from the Object Library on to the story board.
- Now select the table you added and then click Editor\Embed In\Navigation Controller.
- Select the table view within the table view controller, and set the content type to Static Cells in the attributes inspector (as shown in image below).
- Finally, set the number of rows in the table view section (if you
don’t see it, tap on the arrow next to “Table View” in the left sidebar
showing the storyboard hierarchy and then select “Table View Section”)
to 4, and for each row in the table view, set its style to basic and
edit the labels to read:
- Image scroll
- Custom view scroll
- Paged
- Paged with peeking
Scrolling and Zooming a Large Image
The first thing you’re going to learn is how to set up a scroll view that allows the user to zoom into an image and pan around.
First, you need to set up the view controller. Select ViewController.h, add an outlet for a UIScrollView called scrollView and declare that the view controller is going to be a UIScrollView delegate, like so:
Then, synthesize scrollView at the top of ViewController.m, just below the @implementation line:#import
@interface ViewController : UIViewController @property (nonatomic, strong) IBOutlet UIScrollView *scrollView; @end
Back in the storyboard, drag a view controller from the objects list onto the storyboard and set its class to ViewController.@synthesize scrollView = _scrollView;
Now you’re going to get down and dirty with ViewController.m. First you need to declare some properties and methods in the class continuation category at the top of the file (above the @implementation line).
@interface ViewController () @property (nonatomic, strong) UIImageView *imageView; - (void)centerScrollViewContents; - (void)scrollViewDoubleTapped:(UITapGestureRecognizer*)recognizer; - (void)scrollViewTwoFingerTapped:(UITapGestureRecognizer*)recognizer; @end
That should give you a sneak peek of what you’re going to be doing in a moment. But first, synthesize the property you just added.
And now it’s time to get into the most interesting part of setting up the scroll view. Replace viewDidLoad and viewWillAppear: with the following code:@synthesize imageView = _imageView;
This might look complicated, so let’s break it down step-by-step. You’ll see it’s really not too bad.- (void)viewDidLoad { [super viewDidLoad]; // 1 UIImage *image = [UIImage imageNamed:@"photo1.png"]; self.imageView = [[UIImageView alloc] initWithImage:image]; self.imageView.frame = (CGRect){.origin=CGPointMake(0.0f, 0.0f), .size=image.size}; [self.scrollView addSubview:self.imageView]; // 2 self.scrollView.contentSize = image.size; // 3 UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(scrollViewDoubleTapped:)]; doubleTapRecognizer.numberOfTapsRequired = 2; doubleTapRecognizer.numberOfTouchesRequired = 1; [self.scrollView addGestureRecognizer:doubleTapRecognizer]; UITapGestureRecognizer *twoFingerTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(scrollViewTwoFingerTapped:)]; twoFingerTapRecognizer.numberOfTapsRequired = 1; twoFingerTapRecognizer.numberOfTouchesRequired = 2; [self.scrollView addGestureRecognizer:twoFingerTapRecognizer]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; // 4 CGRect scrollViewFrame = self.scrollView.frame; CGFloat scaleWidth = scrollViewFrame.size.width / self.scrollView.contentSize.width; CGFloat scaleHeight = scrollViewFrame.size.height / self.scrollView.contentSize.height; CGFloat minScale = MIN(scaleWidth, scaleHeight); self.scrollView.minimumZoomScale = minScale; // 5 self.scrollView.maximumZoomScale = 1.0f; self.scrollView.zoomScale = minScale; // 6 [self centerScrollViewContents]; }
- First, you need to create an image view with the photo1.png image you added to your project and you set the image view frame (it’s size and position) so it’s the size of the image and sits at point 0,0 within the parent. Finally, the image view gets added as a subview of your scroll view.
- You have to tell your scroll view the size of the content contained within it, so that it knows how far it can scroll horizontally and vertically. In this case, it’s the size of the image.
- Here you’re setting up two gesture recognizers: one for the double-tap to zoom in, and one for the two-finger-tap to zoom out. If you’re unfamiliar with how these work, then I suggest reading this tutorial.
- Next, you need to work out the minimum zoom scale for the scroll view. A zoom scale of one means that the content is displayed at normal size. A zoom scale below one shows the content zoomed out, while a zoom scale of greater than one shows the content zoomed in. To get the minimum zoom scale, you calculate how far you’d need to zoom out so that the image fits snugly in your scroll view’s bounds based on its width. Then you do the same based upon the image’s height. The minimum of those two resulting zoom scales will be the scroll view’s minimum zoom scale. That gives you a zoom scale where you can see the entire image when fully zoomed out.
- You set the maximum zoom scale as 1, because zooming in more than the image’s resolution can support will cause it to look blurry. You set the initial zoom scale to be the minimum, so that the image starts fully zoomed out.
- We’ll come back to this in a bit. For now, just understand that this will center the image within the scroll view.
Now let’s look at those methods that I said you’d implement – centerScrollViewContents, scrollViewDoubleTapped: and scrollViewTwoFingerTapped:. Add the following code above viewDidLoad:
- (void)centerScrollViewContents { CGSize boundsSize = self.scrollView.bounds.size; CGRect contentsFrame = self.imageView.frame; if (contentsFrame.size.width < boundsSize.width) { contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0f; } else { contentsFrame.origin.x = 0.0f; } if (contentsFrame.size.height < boundsSize.height) { contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0f; } else { contentsFrame.origin.y = 0.0f; } self.imageView.frame = contentsFrame; } |
Next up is scrollViewDoubleTapped:. Add this just above viewDidLoad again:
- (void)scrollViewDoubleTapped:(UITapGestureRecognizer*)recognizer { // 1 CGPoint pointInView = [recognizer locationInView:self.imageView]; // 2 CGFloat newZoomScale = self.scrollView.zoomScale * 1.5f; newZoomScale = MIN(newZoomScale, self.scrollView.maximumZoomScale); // 3 CGSize scrollViewSize = self.scrollView.bounds.size; CGFloat w = scrollViewSize.width / newZoomScale; CGFloat h = scrollViewSize.height / newZoomScale; CGFloat x = pointInView.x - (w / 2.0f); CGFloat y = pointInView.y - (h / 2.0f); CGRect rectToZoomTo = CGRectMake(x, y, w, h); // 4 [self.scrollView zoomToRect:rectToZoomTo animated:YES]; } |
- First, you need to work out where the tap occurred within the image view. You’ll use this to zoom in directly on that point, which is probably what you’d expect as a user.
- Next, you calculate a zoom scale that’s zoomed in 150%, but capped at the maximum zoom scale you specified in viewDidLoad.
- Then you use the location from step #1 to calculate a CGRect rectangle that you want to zoom in on.
- Finally, you need to tell the scroll view to zoom in, and here you animate it, as that will look pretty.
- (void)scrollViewTwoFingerTapped:(UITapGestureRecognizer*)recognizer { // Zoom out slightly, capping at the minimum zoom scale specified by the scroll view CGFloat newZoomScale = self.scrollView.zoomScale / 1.5f; newZoomScale = MAX(newZoomScale, self.scrollView.minimumZoomScale); [self.scrollView setZoomScale:newZoomScale animated:YES]; } |
Now, remember how you set up ViewController as a UIScrollView delegate? Well, now you’re going to implement a couple of needed methods for a UIScrollView delegate. The first is viewForZoomingInScrollView. Add the following above viewDidLoad:
- (UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView { // Return the view that you want to zoom return self.imageView; } |
The second method you’ll need to implement is scrollViewDidZoom:, which is a notification when the scroll view has been zoomed. Here you need to re-center the view – if you don’t, the scroll view won’t appear to zoom naturally, instead, it will sort of stick to the top-left. Add this above viewDidLoad:
- (void)scrollViewDidZoom:(UIScrollView *)scrollView { // The scroll view has zoomed, so you need to re-center the contents [self centerScrollViewContents]; } |
Scrolling and Zooming a View Hierarchy
What if you want more than an image in your scroll view? What if you’ve got some complex view hierarchy which you want to be able to zoom and pan around? Well, there’s a scroll view for that! What’s more, it’s just a small step beyond what you’ve done already.Create a new file with the iOS\Cocoa Touch\UIViewController subclass template. Name the class CustomScrollViewController and make sure that Targeted for iPad and With XIB for user interface are not checked. Click Next and save it with the rest of the project.
Open CustomScrollViewController.h and replace the contents with this:
#import |
Also add a scroll view and connect it to the outlet created and set the view controller as its delegate, just as before.
Then, open CustomScrollViewController.m and set up the class continuation category at the top (above the @implementation line) like this:
@interface CustomScrollViewController () @property (nonatomic, strong) UIView *containerView; - (void)centerScrollViewContents; @end |
@synthesize scrollView = _scrollView; @synthesize containerView = _containerView; |
The only other difference compared to the previous view controller is that instead of a UIImageView, we’ve got a UIView and it’s called containerView. That should be a little hint as to how this is all going to work.
Now, implement viewDidLoad and viewWillAppear: like so.
- (void)viewDidLoad { [super viewDidLoad]; // Set up the container view to hold your custom view hierarchy CGSize containerSize = CGSizeMake(640.0f, 640.0f); self.containerView = [[UIView alloc] initWithFrame:(CGRect){.origin=CGPointMake(0.0f, 0.0f), .size=containerSize}]; [self.scrollView addSubview:self.containerView]; // Set up your custom view hierarchy UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 640.0f, 80.0f)]; redView.backgroundColor = [UIColor redColor]; [self.containerView addSubview:redView]; UIView *blueView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 560.0f, 640.0f, 80.0f)]; blueView.backgroundColor = [UIColor blueColor]; [self.containerView addSubview:blueView]; UIView *greenView = [[UIView alloc] initWithFrame:CGRectMake(160.0f, 160.0f, 320.0f, 320.0f)]; greenView.backgroundColor = [UIColor greenColor]; [self.containerView addSubview:greenView]; UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"slow.png"]]; imageView.center = CGPointMake(320.0f, 320.0f); [self.containerView addSubview:imageView]; // Tell the scroll view the size of the contents self.scrollView.contentSize = containerSize; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; // Set up the minimum & maximum zoom scales CGRect scrollViewFrame = self.scrollView.frame; CGFloat scaleWidth = scrollViewFrame.size.width / self.scrollView.contentSize.width; CGFloat scaleHeight = scrollViewFrame.size.height / self.scrollView.contentSize.height; CGFloat minScale = MIN(scaleWidth, scaleHeight); self.scrollView.minimumZoomScale = minScale; self.scrollView.maximumZoomScale = 1.0f; self.scrollView.zoomScale = 1.0f; [self centerScrollViewContents]; } |
viewDidLoad, however, sets up a view hierarchy with a single root view, which is your instance variable, containerView. Then that single view is added to the scroll view. That is the key here – just one view can be added to the scroll view if you’re going to be zooming in, because as you’ll recall, you can only return one view in the delegate callback, viewForZoomingInScrollView:.
Again, implement centerScrollViewContents and the two UIScrollView delegate methods, substituting imageView with containerView from the original versions. (You can add the code above viewDidLoad, as before.)
- (void)centerScrollViewContents { CGSize boundsSize = self.scrollView.bounds.size; CGRect contentsFrame = self.containerView.frame; if (contentsFrame.size.width < boundsSize.width) { contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0f; } else { contentsFrame.origin.x = 0.0f; } if (contentsFrame.size.height < boundsSize.height) { contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0f; } else { contentsFrame.origin.y = 0.0f; } self.containerView.frame = contentsFrame; } - (UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView { // Return the view that we want to zoom return self.containerView; } - (void)scrollViewDidZoom:(UIScrollView *)scrollView { // The scroll view has zoomed, so we need to re-center the contents [self centerScrollViewContents]; } |
