Skip to content

UIScrollView的用法

pro648 edited this page Dec 28, 2017 · 2 revisions

在iOS中,滚动视图UIScrollView用于查看大于屏幕的内容。Scroll View有两个主要目的:

  • 让用户拖动视图以显示更多内容区域。
  • 让用户使用捏合手势放大或缩小所显示的内容。

下图显示了UIScrollView拖动要显示的内容区域,以显示不同内容。UIScrollView上添加一个子视图UIImageViewUIImageView上显示一个男孩的图片。当手指在屏幕上滑动时,屏幕上显示的内容随之移动,滚动条开始显示;当手指离开屏幕,滚动条消失。

indicator

UIScrollView不包含任何视图用以显示其内容,只能滚动其子视图。另外,UITableViewUICollectionViewUITextView均继承自UIScrollView

这篇文章将使用纯代码来学习UIScrollView,使用Interface Builder添加滚动视图和添加其他视图没有区别,这里不再介绍。

1. UIScrollView初体验

UIScrollView创建后需要添加到其他视图控制器或视图层级中,要使用滚动视图,必须配置以下两项:

  • 必须设定contentSize属性。contentSize属性用于指定滚动视图可以滚动的区域。
  • 必须为滚动视图添加一个或多个视图,滚动视图用这些视图显示内容和实现滚动。

其他属性均是可选实现,如:scrollsToToppagingEnabledbouncesrefreshControl等。

自iOS10开始,UIScrollView增加了refreshControl属性,用于把配置好的UIRefreshControl赋值给该属性,这样UIScrollView就有了下拉刷新功能。在此之前,只能在UITableViewController中使用系统提供的下拉刷新功能,想了解具体使用方法可以查看我的另一篇文章:在UIScrollView、UICollectionView和UITableView中添加UIRefreshControl实现下拉刷新

打开Xcode,创建新的工程。选取iOS一栏下Application中的Single View Application模板,点击Next。在Product Name中填写ScrollView,点击Next选择文件位置,点击Create创建工程。

在开始之前,点击这里下载图片,将其添加到Assets.xcassets。打开ViewController.m,添加以下声明。

@interface ViewController ()

@property (strong, nonatomic) UIScrollView *scrollView;
@property (strong, nonatomic) UIImageView *imageView;

@end

使用懒加载初始化以上属性。

- (UIImageView *)imageView {
    if (!_imageView) {
        UIImage *image = [UIImage imageNamed:@"image"];
        // 1.初始化imageView
        _imageView = [[UIImageView alloc] initWithImage:image];
    }
    return _imageView;
}

- (UIScrollView *)scrollView {
    if (!_scrollView) {
        // 2.初始化、配置scrollView
        _scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
        _scrollView.backgroundColor = [UIColor blackColor];
        _scrollView.contentSize = self.imageView.frame.size;
        _scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    }
    return _scrollView;
}

上述代码分步说明如下:

  1. 使用initWithImage: 方法初始化imageView。用这种方法初始化的图片视图其大小与图片大小一致,这里图片大小为2000*1500。
  2. 初始化scrollView时,指定scrollViewframe与当前控制器视图大小、位置一致。设定contentSize为整个imageView的大小。最后设定scrollViewautoresizingMaskUIViewAutoresizingFlexibleWidthUIViewAutoresizingFlexibleHeight,这样在屏幕旋转时,scrollView就可以自动调整布局。

进入viewDidLoad,把scrollView添加到控制器视图,把imageView添加到scrollView

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self.view addSubview:self.scrollView];
    [self.scrollView addSubview:self.imageView];
}

运行demo,现在可以拖动视图查看图像的不同部分。

drag

当滑动到屏幕边缘时再继续滑动(如上面GIF中开始部分),如果bounces属性为YES(默认值),则会反弹;反之,则直接不可滑动。另外,点击状态栏(status bar),滚动视图会直接回到顶部,这是因为scrollsToTop属性默认值为YES。但如果存在多个滚动视图,scrollsToTop属性均为默认值,点击状态栏时将没有任何效果。如果想要获得滑到顶部效果,必须设置其他scrollsToTop属性为NO,只能有一个scrollsToTop属性为YES

滚动视图的origin可以在内容视图上移动,滚动视图按照自身frame裁剪内容视图。滚动视图跟踪手指的移动并相应调整origin,内容视图会根据新的origin绘制内容。滚动视图自身只绘制滚动条,除此之外不绘制任何其他内容。

运行demo时,图片原点显示在scrollView左上角。如果想要修改内容视图显示的内容,需要修改滚动视图的originUIScrollView中的contentOffset属性用于指定内容视图的origin偏离滚动视图origin 的点。默认值为CGPointZero

scrollView的懒加载方法底部,添加以下代码:

- (UIScrollView *)scrollView {
    if (!_scrollView) {
        ...
        _scrollView.contentOffset = CGPointMake(1000, 450);
    }
    return _scrollView;
}

运行demo,可以看到滚动视图已经移动到照片的其他部分。因此,你可以决定加载视图时显示滚动视图的哪一部分。

contentOffset

2. 缩放

我们添加了一个UIScrollView,使用户可以浏览超出屏幕大小的内容视图。如果用户可以放大和缩小视图,将会更加实用。

为支持缩放功能,定义的类必须遵守UIScrollViewDelegate协议,必须实现viewForZoomingInScrollView: 代理方法,在该代理方法中返回要缩放的视图。另外,还需要使用maximumZoomScaleminimumZoomScale指定可应用于滚动视图的最大、最小缩放比。这两个属性的默认值均为1.0

视图控制器遵守UIScrollViewDelegate协议,更新ViewController.m如下:

#import "ViewController.h"
@interface ViewController () <UIScrollViewDelegate>

在实现部分添加以下代理方法,指定可以缩放的视图为imageView

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    return self.imageView;
}

scrollView的懒加载方法中,将当前控制器设置为scrollView的代理,scrollView的最小、最大缩放比分别为0.14.0,当前缩放比zoomScale1.0zoomScale默认值为1.0

- (UIScrollView *)scrollView {
    if (!_scrollView) {
        ...
        
        _scrollView.delegate = self;
        _scrollView.minimumZoomScale = 0.1;
        _scrollView.maximumZoomScale = 4.0;
        _scrollView.zoomScale = 1.0;
    }
    return _scrollView;
}

运行demo,图片将与之前的显示一样。此时,zoomScale1.0。当缩放视图时,可以缩放到minimumZoomScalemaximumZoomScale设定的比例。当图片放大到四倍时,图像会变的模糊 (如下图)。

Blurry

当我们把图片缩小到最小时(此时,zoomScale0.1),会产生一个很小的图像,并在屏幕上产生大量的空白,如下图。

zooming

我们想要让图像在完整显示的前提下,尽可能多的填充scrollView,即imageView使用UIViewContentModeScaleAspectFit效果。为此,我们将使用滚动视图和图像视图大小的比例来计算最小比例因子(scale factor)。

首先移除scrollView懒加载中的以下代码:

        _scrollView.minimumZoomScale = 0.1;
        _scrollView.maximumZoomScale = 4.0;
        _scrollView.zoomScale = 1.0;

在实现部分添加以下方法。先取得scrollViewimageView宽、高比,将minimumZoomScale设置为其中小的值。这里没有设定maximumZoomScale,所以只能放大到默认值1.0,和初始zoomScale默认值相同。

- (void)setZoomScale {
    CGFloat widthScale = CGRectGetWidth(self.scrollView.frame) / CGRectGetWidth(self.imageView.frame);
    CGFloat heightScale = CGRectGetHeight(self.scrollView.frame) / CGRectGetHeight(self.imageView.frame);
    
    self.scrollView.minimumZoomScale = MIN(widthScale, heightScale);
}

最后在viewDidLoad底部调用该方法。

- (void)viewDidLoad {
    ...
    [self setZoomScale];
}

重写viewWillLayoutSubviews方法如下:

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
    
    // 1.移除子视图
    for (UIView *view in self.scrollView.subviews) {
        [view removeFromSuperview];
    }
    
    // 2.初始化imageView 将其添加到scrollView 设置contentSize
    self.imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"image"]];
    [self.scrollView addSubview:self.imageView];
    self.scrollView.contentSize = self.imageView.frame.size;
    
    // 3.重设minimumZoomScale
    [self setZoomScale];
}

上述代码的分步说明如下:

  1. 移除scrollView的所有子视图。
  2. 使用图片image初始化imageView,并将其添加到scrollView,最后重新设定scrollView的可滑动区域contentSize。这样做是为了在设备旋转时,重新计算imageView的位置、大小,同时更新scrollView内容视图大小。
  3. 最后再次调用setZoomScale方法,设定最小缩放比。

运行app,当缩小imageView时,imageView会尽可能占用屏幕上空间,同时可以看到完整的图像视图。

aspectFitPortrait

从上面图像可以看到,图像位于屏幕的左上角,我们希望图像位于屏幕的中心。在代理方法scrollViewDidZoom: 中添加以下代码:

- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
    // 计算imageView缩小到最小时 imageView与屏幕边缘距离
    CGFloat horizontalPadding = CGRectGetWidth(self.imageView.frame) < CGRectGetWidth(scrollView.frame) ? (CGRectGetWidth(scrollView.frame) - CGRectGetWidth(self.imageView.frame)) / 2 : 0 ;
    CGFloat verticalPadding = CGRectGetHeight(self.imageView.frame) < CGRectGetHeight(scrollView.frame) ? (CGRectGetHeight(scrollView.frame) - CGRectGetHeight(self.imageView.frame)) / 2 : 0 ;
    scrollView.contentInset = UIEdgeInsetsMake(verticalPadding, horizontalPadding, verticalPadding, horizontalPadding);
}

每次缩放操作后,系统都会调用该方法用以告诉滚动视图zoomScale已改变。在上面的代码中,我们先计算出图片与屏幕间的填充。对于顶部和底部,先判断imageView的高是否小于scrollView的高,在imageView的高小于scrollView的高时,填充高度为两个视图高之差的二分之一,否则,填充为0;水平方向的填充与此类似。最后设定scrollViewcontentInset属性,该属性用于指定内容视图与滚动视图边缘的距离,单位为point,默认值为UIEdgeInsetsZero

运行demo,在缩小视图至最小时,视图应当位于屏幕的中心。

UIEdgeInsets

在获取视图的宽、高时应当使用CGRectGetWidth()CGRectGetHeight()方法,避免使用self.view.bounds.width直接从CGRect中读取、操作数据。这是因为CGRect数据结构中的宽、高有可能是负数值。比如A矩形CGRectMake(0, 0, 100, 100)与b矩形CGRectMake(100, 100, -100, -100)位置、尺寸完全一致。宽、高中正数值表示向右、向下方向,负数值表示向左、向上方向。通过调用CGRectGetWidthCGRectGetHeight方法获得width、height为正数值。

滚动视图默认支持了UIPanGestureRecognizerUIPinchGestureRecognizer。想要了解更多手势使用,查看我的另一篇文章:手势控制:点击、滑动、平移、捏合、旋转、长按、轻扫

3. 捏合手势

添加几行代码就可以让UIScrollView支持捏合手势。在iOS Human Interface Guidlines中建议双击放大或缩小视图,但这一约定默认了视图只有一个缩放等级,如在相册应用中双击把图片放大至最大,再次双击把图片缩小至最小。或者双击会将视图放大至最大,一旦放大到最大后再次双击,视图将缩小至全屏。但也有一些应用在使用双击放大这一功能时会有特殊需求,例如地图应用。地图应用需要双击放大视图,再次双击继续放大视图。为了缩小视图,需要两个手指在屏幕上滑动,当手指靠近时视图缩小。

为了让应用支持点击缩放功能,需要在遵守UIScrollViewDelegate协议的类中添加响应手势的方法。当该类检测到点击、双击、两个手指触摸动作时,会触发对应响应操作。

在这个demo将实现以下功能:当视图最小时,双击视图放大至maximumZoomScale;否则,双击缩小视图至minimumZoomScale。与日常使用中的相册应用一致。

viewDidLoad中为scrollView添加双击手势。

- (void)viewDidLoad {
    ...
    // 添加双击手势
    UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)];
    doubleTap.numberOfTapsRequired = 2;
    [self.scrollView addGestureRecognizer:doubleTap];
}

在实现部分添加双击手势响应方法。

- (void)handleDoubleTap:(UITapGestureRecognizer *)doubleTap {
    if (self.scrollView.zoomScale > self.scrollView.minimumZoomScale) {
        // 视图大于最小视图时 双击将视图缩小至最小
        [self.scrollView setZoomScale:self.scrollView.minimumZoomScale animated:YES];
    }
    else
    {
        // 视图为最小时 双击将视图放大至最大
        [self.scrollView setZoomScale:self.scrollView.maximumZoomScale animated:YES];
    }
}

在上面代码中,我们根据当前zoomScale进行放大或缩小视图的操作。

运行demo,可以通过双击放大、缩小视图。

doubleTap

观察上面GIF你会发现,使用双击放大手势点击scrollView任意位置,图像均以自身的中心为基点进行放大操作,而不是以点击位置为中心进行放大。我们在查看照片时,更希望视图以点击位置为中心进行放大。

下面实现以点击位置为中心放大图片。更新handleDoubleTap: 方法如下:

- (void)handleDoubleTap:(UITapGestureRecognizer *)doubleTap {
    ...
    else
    {
        /*
        // 视图为最小时 双击将视图放大至最大
        [self.scrollView setZoomScale:self.scrollView.maximumZoomScale animated:YES];
        */
        
        // 1.获取点击位置
        CGPoint touchPoint = [doubleTap locationInView:self.imageView];
        // 2.获取要显示的imageView区域
        CGRect zoomRect = [self zoomRectForScrollView:self.scrollView withScale:self.scrollView.maximumZoomScale withCenter:touchPoint];
        // 5.将要显示的imageView区域显示到scrollView
        [self.scrollView zoomToRect:zoomRect animated:YES];
    }
}

- (CGRect)zoomRectForScrollView:(UIScrollView *)scrollView withScale:(CGFloat)scale withCenter:(CGPoint)center {
    // 3.声明一个区域 滚动视图的宽除以放大倍数可以得到要显示imageView宽度
    CGRect zoomRect;
    zoomRect.size.width = CGRectGetWidth(scrollView.frame) / scale;
    zoomRect.size.height = CGRectGetHeight(scrollView.frame) / scale;
    
    // 4.点击位置x坐标减去1/2图像宽度,可以得到要显示imageView的原点x坐标 y坐标类似
    zoomRect.origin.x = center.x - zoomRect.size.width / 2;
    zoomRect.origin.y = center.y - zoomRect.size.height / 2;
    
    return zoomRect;
}

上述代码分布说明如下:

  1. 获取点击位置在imageView坐标系统中的位置。
  2. 调用zoomRectForScrollView: withScale: withCenter: 方法,获取要显示的imageView区域。
  3. 先声明一个区域用以表示要返回的imageView可见区域。scrollView的宽除以放大倍数得到要显示的imageView原图的宽,即zoomRect的宽。取得高的方法也是如此。
  4. 点击位置x坐标减去1/2要显示imageView宽(即zoomRect的宽),可以得到zoomRect原点x坐标。y坐标取得过程类似。
  5. 使用zoomRect: animated: 方法把要显示的imageViewzoomRect区域发送给scrollView。这里的zoomRect一定是imageView坐标系统上的区域。

zoomToRect:animated:的rect参数必须为viewForZoomingInScrollView:方法返回视图的坐标系统,如果rect大小与content view大小不同,会自动调整zoomScale

scrollRectToVisible:animated:的rect参数使用scroll view坐标系统。如果rect视图已经显示在当前屏幕,则该方法什么也不做。

运行demo,点击不同位置,查看放大效果。

zoomRect:

4. 翻页浏览

UIScrollView支持分页模式。在该模式下,用户每滑动一次手指,移动单个屏幕内所有内容。该模式一般用在电子书或引导页中。

除了像上面那样配置滚动视图,还需要把BOOL类型的pagingEnabled属性设置为YES。该属性用于指定滚动视图是否开启分页。contentSize属性中的高为滚动视图的高,宽为滚动视图的宽乘页数。另外,一般滚动视图的滚动条会被禁用,如果想要显示进度,可以使用UIPageControl。下图显示了开启分页模式的滚动视图滑动过程。

PageMode

使用快捷键command+N添加文件,选取弹出框中iOS一栏下Source中的Cocoa Touch Class模板,点击NextClassPageViewControllerSubclass ofUIViewController,点击Next。选择文件位置,点击Create创建文件。我们将在这个文件内实现翻页浏览功能。

在这一部分我们将在PageViewController上添加一个滚动视图,水平滑动显示上一页、下一页视图,滚动视图上显示当前页的图片,页面底部将会显示UIPageControl

page

开始之前先添加这一部分所需五张图片,可以通过文章底部的网址下载源码获取。

进入PageViewController.m,添加以下声明。

@interface PageViewController ()

@property (strong, nonatomic) UIScrollView *scrollView;
@property (strong, nonatomic) NSArray *contentList;
@property (strong, nonatomic) UIPageControl *pageControl;

@end

其中,数组contentList内包含要显示照片名称。

在实现部分前添加预定义,用于指定pageControl的高,同时也是scrollView与顶部距离。

#define pageControlHeight 70

使用懒加载初始化scrollViewpageControl

- (UIScrollView *)scrollView {
    if (!_scrollView) {
        _scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, pageControlHeight, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) - 2*pageControlHeight)];
        _scrollView.contentSize = CGSizeMake(CGRectGetWidth(self.view.frame) * self.contentList.count, CGRectGetHeight(self.view.frame) - 2*pageControlHeight);
        _scrollView.backgroundColor = [UIColor whiteColor];
        _scrollView.pagingEnabled = YES;
        _scrollView.delegate = self;
        _scrollView.showsHorizontalScrollIndicator = NO;
    }
    return _scrollView;
}

- (UIPageControl *)pageControl {
    if (!_pageControl) {
        _pageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(0, CGRectGetHeight(self.view.frame) - pageControlHeight, CGRectGetWidth(self.view.frame), pageControlHeight)];
        _pageControl.numberOfPages = self.contentList.count;
        _pageControl.currentPage = 0;
    }
    return _pageControl;
}

scrollViewframe就是上面GIF图中的白色背景部分,这里设定contentSize时其宽度为滚动视图宽度的整数倍,整数由页数决定。最后把pagingEnabled属性设置为YES

pageControl的总页数由数组contentList内元素个数决定。开始时,默认显示第一页,即currentPage属性为0

此时,系统会在_scrollView.delegate = self一行处发出内容为*Assigning to 'id_Nullable'from incompatible type 'PageViewController *const _strong'*的警告,这是因为我们没有遵守UIScrollDelegate协议。在PageViewController.m的interface添加该协议。

@interface PageViewController () <UIScrollViewDelegate>

viewDidLoad方法中初始化数组,添加scrollViewpageControl到控制器视图,为scrollView每一页添加图片。更新后如何:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 1.初始化数组
    self.contentList = @[@"one", @"two", @"three", @"four", @"five"];
    
    // 2.将scrollView和pageControl添加到view 设定控制器背景颜色
    [self.view addSubview:self.scrollView];
    [self.view addSubview:self.pageControl];
    self.view.backgroundColor = [UIColor blackColor];
    
    // 3.为scrollView每一页添加图片
    for (NSUInteger i=0; i<self.contentList.count; ++i) {
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(CGRectGetWidth(self.scrollView.frame) * i, 0, CGRectGetWidth(self.scrollView.frame), CGRectGetHeight(self.scrollView.frame))];
        imageView.contentMode = UIViewContentModeScaleAspectFit;
        imageView.image = [UIImage imageNamed:self.contentList[i]];
        [self.scrollView addSubview:imageView];
    }
}

上述代码分步说明如下:

  1. 设定数组内元素,这些元素会在后面用作每一页图片的名称。
  2. scrollViewpageControl添加到控制器视图,设定背景颜色为黑色。
  3. 使用for循环为每一页内容添加图片。每一页图片的宽和高与scrollView的宽高相同,原点x为滚动视图宽乘i,因为要添加的图片在滚动视图内,其坐标系统为滚动视图,所以原点坐标y为0。设定imageViewcontentModeUIViewContentModeScaleAspectFit。之后为imageView添加图片。最后添加imageViewscrollView

在代理方法scrollViewDidEndDecelerating: 中,根据当前位置计算出当前视图所处的页数,最后更新pageControl的当前页码currentPage

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    CGFloat pageWidth = CGRectGetWidth(self.scrollView.frame);
    NSUInteger page = floor((scrollView.contentOffset.x - pageWidth/2)/pageWidth) + 1;
    self.pageControl.currentPage = page;
}

开启分页后,scrollView的偏移坐标x只有在超过了该页的中点后,才会进入新的一页,否则将会停留在上一页。计算出当前所处页后更新pageControlcurrentPage属性。

在运行demo前,先指定PageViewController为根视图控制器,否则,运行结果还是前一部分的视图。

打开AppDelegate.m,导入PageViewController.h,并在application: didFinishLaunchingWithOptions: 方法中设定根控制器。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    // 设置PageViewController为根控制器
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.window.rootViewController = [[PageViewController alloc] init];
    [self.window makeKeyAndVisible];
    
    return YES;
}

添加上面代码后运行demo。

test

可以看到,上面的demo存在这两个问题:

  1. 因为背景色为黑色,导致顶部状态栏的时间、运营商等信息完全看不清。
  2. 点击pageControl时,虽然currentPage属性会自动更新,但滚动视图中的图片并没有更新。

首先添加以下代码,将状态栏的文字改为白色。

- (UIStatusBarStyle)preferredStatusBarStyle {
    return UIStatusBarStyleLightContent;
}

pageControl中添加点击事件,当接收到UIControlEventValueChanged事件时,更新滚动视图中图片。

- (UIPageControl *)pageControl {
    if (!_pageControl) {
        ...
        [_pageControl addTarget:self action:@selector(changePage:) forControlEvents:UIControlEventValueChanged];
    }
    return _pageControl;
}

- (void)changePage:(id)sener {
    NSUInteger page = self.pageControl.currentPage;
    CGRect bounds = self.scrollView.bounds;
    bounds.origin.x = CGRectGetWidth(bounds) * page;
    bounds.origin.y = self.scrollView.frame.origin.y;
    [self.scrollView scrollRectToVisible:bounds animated:YES];
}

在点击事件的响应方法changePage: 中,根据当前的currentPage计算出目前页码的frame,该frame的坐标系统为scrollView。最后使用scrollRectToVisible: 方法滑动当前视图到该区域。

完成后运行如下:

page

在上面的视图中,内容视图上内容很少,我们可以在一个视图上(即滚动视图的contentSize)一次绘制所有内容。这样很简单,但在处理内容视图上有很多内容(如电子书有成千上万页),或内容视图的绘制非常耗费时间时,就会变得非常低效。这时你需要使用多个视图,每个视图只显示一页内容。如一个视图显示当前页内容,一个视图显示上一页内容,另一个视图显示下一页内容,当用户滑动视图时,循环使用这些视图。这里不再详细说明,想要详细了解可以点击这里查看。

5. 嵌套滚动视图

为提供更丰富的使用体验,可以在应用中嵌套使用UIScrollView

嵌套的滚动视图,可以分为相同方向滚动和滚动方向相差90度的滚动。如下图:

Nested

嵌套类滚动视图的一个应用示例是股票类应用程序。在使用嵌套滚动视图时,我们不需要做任何额外操作,系统默认支持该功能。

Demo名称:ScrollView
源码地址:https://github.com/pro648/BasicDemos-iOS

参考资料:

  1. A Beginner’s Guide to UIScrollView
  2. About Scroll View Programming
  3. PageControl
  4. CGGeometry
Clone this wiki locally