-
Notifications
You must be signed in to change notification settings - Fork 472
Scroll View Guide
Scroll views allow you to display content in an area on the screen that
is smaller than the size of the content. They allow the user to pan
(scroll) and/or zoom the content. The
UITableView
and UICollectionView
are a subclasses of UIScrollView
, so many of the
points below will apply to them as well.
Add a scroll view to your view controller by dragging one from the Object Library. The size of the scroll view is the size of the area that is visible to the user.
Tell the scroll view about the size of your content area by setting the
contentSize
property. In this example the content has the same width
as the scroll view and three times its height. The user will have to
scroll vertically to access the lower parts of the content area.
class ViewController: UIViewController {
@IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
let contentWidth = scrollView.bounds.width
let contentHeight = scrollView.bounds.height * 3
scrollView.contentSize = CGSize(width: contentWidth, height: contentHeight)
...
}
}
// ViewController.h
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@end
// ViewController.m
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
CGFloat contentWidth = self.scrollView.bounds.size.width;
CGFloat contentHeight = self.scrollView.bounds.size.height * 3;
self.scrollView.contentSize = CGSizeMake(contentWidth, contentHeight);
// ...
}
@end
What is shown in the scrollable content area of a scroll view is determined by its subviews. The frame of each subview is relative to the top left of the content area. A scroll view can contain more than one subview. In this example we add a series differently-colored subviews. Each subview has the same width as the scroll view, but we offset the top of their frames so they do not overlap each other.
class ViewController: UIViewController {
@IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
let contentWidth = scrollView.bounds.width
let contentHeight = scrollView.bounds.height * 3
scrollView.contentSize = CGSize(width: contentWidth, height: contentHeight)
let subviewHeight = CGFloat(120)
var currentViewOffset = CGFloat(0);
while currentViewOffset < contentHeight {
let frame = CGRectMake(0, currentViewOffset, contentWidth, subviewHeight).rectByInsetting(dx: 5, dy: 5)
let hue = currentViewOffset/contentHeight
let subview = UIView(frame: frame)
subview.backgroundColor = UIColor(hue: hue, saturation: 1, brightness: 1, alpha: 1)
scrollView.addSubview(subview)
currentViewOffset += subviewHeight
}
}
}
// ViewController.m
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
CGFloat contentWidth = self.scrollView.bounds.size.width;
CGFloat contentHeight = self.scrollView.bounds.size.height * 3;
self.scrollView.contentSize = CGSizeMake(contentWidth, contentHeight);
CGFloat subviewHeight = (CGFloat)120;
CGFloat currentViewOffset = (CGFloat)0;
while (currentViewOffset < contentHeight) {
CGRect frame = CGRectInset(CGRectMake(0, currentViewOffset, contentWidth, subviewHeight), 5, 5);
CGFloat hue = currentViewOffset/contentHeight;
UIView *subview = [[UIView alloc]initWithFrame:frame];
subview.backgroundColor = [UIColor colorWithHue:hue saturation:1 brightness:1 alpha:1];
[self.scrollView addSubview:subview];
currentViewOffset += subviewHeight;
}
}
@end
Here's what our example looks like when running:
You can programatically scroll the contents a scroll view by setting its
contentOffset
property.
class ViewController: UIViewController {
@IBOutlet weak var scrollView: UIScrollView!
...
@IBAction func didTapDownButton(sender: AnyObject) {
let newOffset = CGPointMake(scrollView.contentOffset.x, scrollView.contentOffset.y + 300)
scrollView.setContentOffset(newOffset, animated: true)
}
}
// ViewController.m
@implementation ViewController
...
- (IBAction)didTapDownButton:(id)sender {
CGPoint newOffset = CGPointMake(self.scrollView.contentOffset.x, self.scrollView.contentOffset.y + 300);
[self.scrollView setContentOffset:newOffset animated:YES];
}
@end
If you want to make sure a given view is visible on screen you can
you can use the scrollRectToVisible
method.
class ViewController: UIViewController {
@IBOutlet weak var scrollView: UIScrollView!
var grayView: UIView!
override func viewDidLoad() {
...
grayView = UIView(frame: CGRectMake(50, 620, scrollView.contentSize.width - 100, 150))
grayView.backgroundColor = UIColor.grayColor()
scrollView.addSubview(grayView)
}
@IBAction func didTapScrollButton(sender: AnyObject) {
scrollView.scrollRectToVisible(grayView.frame, animated: true)
}
}
// ViewController.m
@implementation ViewController
UIView *grayView;
- (void)viewDidLoad {
...
grayView = [[UIView alloc]initWithFrame:CGRectMake(50, 620, self.scrollView.contentSize.width - 100, 150)];
grayView.backgroundColor = UIColor.grayColor;
[self.scrollView addSubview:grayView];
}
- (IBAction)didTapScrollButton:(id)sender {
[self.scrollView scrollRectToVisible:grayView.frame animated:YES];
}
@end
You can force the scroll view to to snap to "page" boundaries by setting
the pagingEnabled
property. The size of a page is equal to the size
of the scroll view. You'll generally want to disable to scroll
indicators when paging is enabled. Here we allow the user to
horizontally page through three views.
class ViewController: UIViewController, UIScrollViewDelegate {
@IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
let pageWidth = scrollView.bounds.width
let pageHeight = scrollView.bounds.height
scrollView.contentSize = CGSizeMake(3*pageWidth, pageHeight)
scrollView.pagingEnabled = true
scrollView.showsHorizontalScrollIndicator = false
let view1 = UIView(frame: CGRectMake(0, 0, pageWidth, pageHeight))
view1.backgroundColor = UIColor.blueColor()
let view2 = UIView(frame: CGRectMake(pageWidth, 0, pageWidth, pageHeight))
view2.backgroundColor = UIColor.orangeColor()
let view3 = UIView(frame: CGRectMake(2*pageWidth, 0, pageWidth, pageHeight))
view3.backgroundColor = UIColor.purpleColor()
scrollView.addSubview(view1)
scrollView.addSubview(view2)
scrollView.addSubview(view3)
}
}
// ViewController.m
@implementation ViewController
...
- (void)viewDidLoad {
[super viewDidLoad];
CGFloat pageWidth = self.scrollView.bounds.size.width;
CGFloat pageHeight = self.scrollView.bounds.size.height;
self.scrollView.contentSize = CGSizeMake(3*pageWidth, pageHeight);
self.scrollView.pagingEnabled = YES;
self.scrollView.showsHorizontalScrollIndicator = NO;
UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, pageWidth, pageHeight)];
view1.backgroundColor = UIColor.blueColor;
UIView *view2 = [[UIView alloc] initWithFrame:CGRectMake(pageWidth, 0, pageWidth, pageHeight)];
view2.backgroundColor = UIColor.orangeColor;
UIView *view3 = [[UIView alloc] initWithFrame:CGRectMake(2*pageWidth, 0, pageWidth, pageHeight)];
view3.backgroundColor = UIColor.purpleColor;
[self.scrollView addSubview:view1];
[self.scrollView addSubview:view2];
[self.scrollView addSubview:view3];
}
@end
The paging behavior is often combined with a UIPageControl
that
displays a dot for each page and allows the user to switch pages by
tapping. This behavior can be achieved by implementing the
UIScrollViewDelegate
and adding an action handler for the page
control.
class ViewController: UIViewController, UIScrollViewDelegate {
@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var pageControl: UIPageControl!
override func viewDidLoad() {
...
scrollView.delegate = self
pageControl.numberOfPages = 3
}
@IBAction func pageControlDidPage(sender: AnyObject) {
let xOffset = scrollView.bounds.width * CGFloat(pageControl.currentPage)
scrollView.setContentOffset(CGPointMake(xOffset,0) , animated: true)
}
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
pageControl.currentPage = Int(scrollView.contentOffset.x / scrollView.bounds.width)
}
}
// ViewController.h
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController <UIScrollViewDelegate>
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@property (weak, nonatomic) IBOutlet UIPageControl *pageControl;
- (IBAction)pageControlDidPage:(id)sender
@end
// ViewController.m
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
...
self.scrollView.delegate = self;
self.pageControl.numberOfPages = 3;
}
- (IBAction)pageControlDidPage:(id)sender {
CGFloat xOffset = self.scrollView.bounds.size.width * (CGFloat)self.pageControl.currentPage;
[self.scrollView setContentOffset:CGPointMake(xOffset, 0) animated:YES];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
self.pageControl.currentPage = (NSInteger)self.scrollView.contentOffset.x/self.scrollView.bounds.size.width;
}
@end
Basic zooming behavior via pinch gestures is provided by the scroll view
if you implement the viewForZoomingInScrollView
delegate method.
Since a scroll view can have more than one subview, this method will
identify which view will be zoomed when the pinch gesture fires. You'll
also need to set the minimumZoomScale
and maximumZoomScale
.
class ViewController: UIViewController, UIScrollViewDelegate {
@IBOutlet weak var scrollView: UIScrollView!
var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
scrollView.minimumZoomScale = 0.25
scrollView.maximumZoomScale = 2
let image = UIImage(named: "romanesco-broccoli")
imageView = UIImageView(image: image)
scrollView.contentSize = image!.size
scrollView.addSubview(imageView)
scrollView.zoomScale = 0.5
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return imageView
}
}
// ViewController.m
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.scrollView.delegate = self;
self.scrollView.minimumZoomScale = 0.25;
self.scrollView.maximumZoomScale = 2;
UIImage *image = [UIImage imageNamed:@"romanesco-broccoli"];
imageView = [[UIImageView alloc]initWithImage:image];
self.scrollView.contentSize = image.size;
[self.scrollView addSubview:imageView];
self.scrollView.zoomScale = 0.5;
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView{
return imageView;
}
@end
As noted above, there are two sizes that are relevant when working with a scroll view:
- the scroll view's (frame) size. This determines how the scroll view fits inside its parent view and its size as it appears on the screen to the user.
- the scroll view's content size. This determines the size of available to the scrollable content within the scroll view.
In order to use a scroll view with auto layout you must still specify both of these sizes.
One approach to accomplishing this is to use auto layout constraints only to determine the scroll view's size relative to its parents and neighbors. The content size and size of subviews inside the scroll view are still set programatically as we have been doing throughout this guide.
A second approach uses only Auto Layout constraints. In particular, this means there must be a way for Auto Layout to specify the content size of a scroll view. Altogether, the constraints must be enough to determine three things:
- The size of the scroll view. You can use the pin and align tools to create constraints to parent and sibling views. Unlike other views, you cannot rely on the size of child views of the scroll view here.
- The content size of the scroll view. This is specified by creating constraints between the scroll view's edges and the subviews within the scroll view. Note that these constraints have a different meaning than normal constraints between a parent view and its child views. The only size being determined here is the content size of the scroll view.
- Finally you must set constraints to determine the size of the actual subviews containing the scrollable content. These constraints cannot depend on the edges of the scroll view—since as we just saw, they would then be interpreted as constraints on the content size. It is typical here to set a fixed size for a single main scrollable subview and to place other views inside this subview.
A comprehensive discussion of this topic can be found here.