- 當(dāng)展示的內(nèi)容較多,超出一個屏幕時,用戶可通過滾動手勢來查看屏幕以外的內(nèi)容
- 普通的UIView不具備滾動功能,不適合顯示過多的內(nèi)容
- UIScrollView是一個能夠滾動的視圖控件,可以用來展示大量的內(nèi)容,并且可以通過滾動查看所有的內(nèi)容
- UIScrollView是一個可以處理滾動操作的視圖,UIScrollView在開發(fā)過程中使用很頻繁,而且它也經(jīng)常作為其他控件的子控件,例如UITableView就繼承自UIScrollView。
常見屬性
| 屬性 | 說明 |
|---|---|
| @property(nonatomic) CGPoint contentOffset; | 內(nèi)容偏移量,當(dāng)前顯示的內(nèi)容的頂點相對此控件頂點的x、y距離,默認(rèn)為CGPointZero |
| @property(nonatomic) CGSize contentSize; | 控件內(nèi)容大小,不一定在顯示區(qū)域,如果這個屬性不設(shè)置,此控件無法滾動,默認(rèn)為CGSizeZero |
| @property(nonatomic) UIEdgeInsets contentInset; | 控件四周邊距,類似于css中的margin,注意邊距不作為其內(nèi)容的一部分,默認(rèn)為UIEdgeInsetsZero |
| @property(nonatomic,assign) id<UIScrollViewDelegate> delegate; | 控件代理,一般用于事件監(jiān)聽,在iOS中多數(shù)控件都是通過代理進行事件監(jiān)聽的 |
| @property(nonatomic) BOOL bounces; | 是否啟用彈簧效果,啟用彈簧效果后拖動到邊緣可以看到內(nèi)容后面的背景,默認(rèn)為YES |
| @property(nonatomic,getter=isPagingEnabled) BOOL pagingEnabled; | 是否分頁,如果分頁的話每次左右拖動則移動寬度是屏幕寬度整數(shù)倍,默認(rèn)為NO |
| @property(nonatomic,getter=isScrollEnabled) BOOL scrollEnabled; | 是否啟用滾動,默認(rèn)為YES |
| @property(nonatomic) BOOL showsHorizontalScrollIndicator; | 是否顯示橫向滾動條,默認(rèn)為YES |
| @property(nonatomic) BOOL showsVerticalScrollIndicator; | 是否顯示縱向滾動條,默認(rèn)為YES |
| @property(nonatomic) CGFloat minimumZoomScale; | 最小縮放倍數(shù),默認(rèn)為1.0 |
| @property(nonatomic) CGFloat maximumZoomScale; | 最大縮放倍數(shù)(注意只有maximumZoomScale大于minimumZoomScale才有可能縮放),默認(rèn)為1.0 |
| @property(nonatomic,readonly,getter=isTracking) BOOL tracking; | (狀態(tài))是否正在被追蹤,手指按下去并且還沒有拖動時是YES,其他情況均為NO |
| @property(nonatomic,readonly,getter=isDragging) BOOL dragging; | 是否正在被拖拽 |
| @property(nonatomic,readonly,getter=isDecelerating) BOOL decelerating; | 是否正在減速 |
| @property(nonatomic,readonly,getter=isZooming) BOOL zooming; | 是否正在縮放 |
scrollView不能滾動的幾種情況
1.沒有設(shè)置contentSize
2.scrollEnabled屬性 = NO
3.userInteractionEnabled屬性 = NO-
enabled和userInteractionEnabled的區(qū)別
- enabled: 代表控件不可用
- userInteractionEnabled: 代表控件不可以和用戶交互,也就是不能響應(yīng)用戶的操作

contentInset相當(dāng)于在內(nèi)容大小contentSize的基礎(chǔ)上增加了一塊額外的滾動區(qū)域,是額外的,所以和contentSize、contentOffset都沒有直接的關(guān)系。
contentOffset
下面用圖來表達contentOffset,其中,綠色矩形是scrollview,透明紅矩形是scrollview的內(nèi)容


- 如圖1,內(nèi)容左上角是(-100, -100),則
contentOffset = (0, 0) - (-100, -100) = (0 - -100, 0 - -100) = (100, 100) - 如圖2,內(nèi)容的左上角是(100, 100),則
contentOffset = (0, 0) - (100, 100) = (0 - 100, 0 - 100) = (-100, -100) - 所以,contentOffset = 控件左上角(0, 0) - 內(nèi)容左上角
記??!
contentOffset = 控件左上角(0, 0) - 內(nèi)容左上角
contentOffset.x > 0 ,內(nèi)容左移
contentOffset.x < 0 ,內(nèi)容右移
contentOffset.y > 0 ,內(nèi)容上移
contentOffset.y < 0 ,內(nèi)容下移
常用方法
| 方法 | 說明 |
|---|---|
| - (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated; | 設(shè)置滾動位置,第二個參數(shù)表示是否啟用動畫效果 |
| - (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated; | 滾動并顯示指定區(qū)域的內(nèi)容,第二個參數(shù)表示是否啟用動畫效果 |
常見代理方法
| 代理方法 | 說明 |
|---|---|
| - (void)scrollViewDidScroll:(UIScrollView *)scrollView; | 滾動事件方法,滾動過程中會一直循環(huán)執(zhí)行(滾動中…) |
| - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView; | 開始拖拽事件方法 |
| - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate; | 拖拽操作完成事件方法 |
| - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView; | 即將停止?jié)L動事件方法(拖拽松開后開始減速時執(zhí)行) |
| - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView; | 滾動停止事件方法(滾動過程中減速停止后執(zhí)行) |
| - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view NS_AVAILABLE_IOS(3_2); | 開始縮放事件方法 |
| - (void)scrollViewDidZoom:(UIScrollView *)scrollView NS_AVAILABLE_IOS(3_2); | 縮放操作完成事件方法 |
| - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView; | 返回縮放視圖,注意只有實現(xiàn)這個代理方法才能進行縮放,此方法返回需要縮放的視圖 |
方法的調(diào)用順序
拖動UIScrollview
如果我們拖動一個UIScrollView中的子控件移動的時候它的執(zhí)行順序如下:
- 開始拖拽,
- 滾動...,
- 停止拖拽,
- 將要停止?jié)L動,
- 滾動...
- 停止?jié)L動
其中步驟4、5和6有可能執(zhí)行也有可能不執(zhí)行,關(guān)鍵看你拖拽的停止的時候是突然停止(沒有慣性)還是有一段慣性讓他繼續(xù)執(zhí)行(有慣性)。但是不管怎么樣滾動事件會一直執(zhí)行,因此如果在這個事件中進行某種操作一定要注意性能。
- 如果想在UIScrollView停止?jié)L動之后做一些操作, 有兩種情況
- 沒有慣性的情況:只會調(diào)用 停止拖拽的方法,不會調(diào)用停止減速的方法
- 有慣性的情況:既會調(diào)用 停止拖拽的方法,也會調(diào)用停止減速的方法
所以:以后要判斷UIScrollView是否停止?jié)L動,需要同時重寫兩個方法
2.1 scrollViewDidEndDragging
2.2 scrollViewDidEndDecelerating
- 優(yōu)化停止方法,使得只需寫一次滾動停止的操作
// 結(jié)束拖拽
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (decelerate == NO) { // 如果不是減速,也就是沒有慣性
// 主動調(diào)用結(jié)束減速方法,所有的滾動停止后操作都在那個方法中執(zhí)行
[self scrollViewDidEndDecelerating:scrollView];
}
}
// 結(jié)束減速
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
// 在這里寫停止?jié)L動后的相關(guān)操作
}
縮放UIScrollview
- 如果我們縮放UIScrollView的子控件的時候它的執(zhí)行順序如下:
- 開始縮放
- 滾動…
- 停止縮放
- 同樣在這個過程中滾動事件會一直調(diào)用(當(dāng)然如果縮放過程中手指有別的動作也可能會觸發(fā)其他事件,這個大家可以自己體會)。
// 1.設(shè)置縮放大小
srcollView.maximumZoomScale = 2.0;
srcollView.minimumZoomScale = 1;
// 2.設(shè)置需要縮放的控件
-(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return self.imageView;
}
無限循環(huán)輪播器
在UIScrollView中如果放置其他控件后,只要設(shè)置contentSize之后這些圖片就可以滾動。如果要讓圖片無限循環(huán)那么只有兩種辦法,一種是無限循環(huán)疊加圖片,另一種就是如果最后一張圖片瀏覽完立即顯示第一張圖片。顯然第一種明顯不合適,考慮第二種。
使用第二種原理其實很簡單,就是前后各加一張(n+2)。比如有5張圖片那么久需要7個UIImageView,圖片分別是圖片5,圖片1,圖片2,圖片3,圖片4,圖片5,圖片0。
當(dāng)從圖片1滾動到圖片5時由于最后一張是圖片1就給用戶一種無限循環(huán)的感覺,當(dāng)這張圖完全顯示后我們迅速將UIScrollView的contentOffset設(shè)置到第二個UIImageView,也就是圖片1,接著用戶可以繼續(xù)向后滾動。當(dāng)然向前滾動原理完全一樣,當(dāng)滾動到第一張圖片(圖片5)就迅速設(shè)置UIScrollView的contentOffset顯示第6張圖(圖片5)。
性能優(yōu)化
用上面的方法的話,需要過多這些圖片,而這樣勢必全部加載到內(nèi)存,這是我們不愿意看到的,此時需要優(yōu)化。其實只需要3個UIImageView(2個也可以)同樣可以實現(xiàn)上面的效果。只是每次滾動后,都需要重新設(shè)置圖片。例如開始的時候,是圖片5,圖片1,圖片2。向后滾動,那么三種圖分別的序號就是1,2,3,然后通過contentOffset設(shè)置顯示中間的UIImageView。當(dāng)然,向前滾動原理完全一樣,如此就給用戶一種循環(huán)錯覺,而且不占用過多內(nèi)存。
在程序中,讀取寫入了圖片信息的plist文件并加載對應(yīng)的圖片,圖片按順序依次命名:page0.jpg、page1.jpg…page4.jpg。我們的程序主要集中于自定義的ViewController.m中,在這里我們聲明1個UIScrollView和3個UIImageView用于顯示圖片,同時聲明一個UIPageControl來顯示當(dāng)前圖片頁數(shù),具體代碼如下:
#import "ViewController.h"
@interface ViewController ()<UIScrollViewDelegate>
/** 所有圖片*/
@property (nonatomic, strong) NSDictionary *images;
/** 圖片個數(shù)*/
@property (nonatomic, assign) int imageCount;
/** 輪播器*/
@property (nonatomic, weak) UIScrollView *cycleScrollView;
/** 左邊的圖控件*/
@property (nonatomic, weak) UIImageView *leftImageView;
@property (nonatomic, weak) UIImageView *midImageView;
@property (nonatomic, weak) UIImageView *rightImageView;
/** 圖片索引*/
@property (nonatomic, assign) int imageIndex;
// 包裝view需要裝下scrollview和pageControl
/** 包裝view*/
@property (nonatomic, weak) UIView *backGroundView;
/** 分頁控件*/
@property (nonatomic, weak) UIPageControl *pageC;
@end
@implementation ViewController
#define SCREEN_WIDTH self.view.bounds.size.width
#define SCREEN_HEIGHT self.view.bounds.size.height
#define IMAGEVIEW_COUNT 3
#define ScrollView_HEIGHT (200 * (SCREEN_WIDTH) / 320)
- (void)viewDidLoad {
[super viewDidLoad];
// 加載圖片數(shù)據(jù)
[self loadImageData];
// 添加滾動圖
[self createScrollView];
// 給滾動圖添加圖片控件
[self addImageViewToScrollView];
// 添加分頁控件
[self addPageControl];
// 加載默認(rèn)圖片
[self setDefaultImages];
}
// 加載圖片數(shù)據(jù)
-(void)loadImageData {
NSString *path = [[NSBundle mainBundle] pathForResource:@"CycleScrollImages" ofType:@"plist"];
self.images = [NSDictionary dictionaryWithContentsOfFile:path];
self.imageCount = (int)self.images.count;
}
// 添加滾動圖
-(void)createScrollView {
// 創(chuàng)建包含的view
UIView *bV = [[UIView alloc] init];
bV.frame = CGRectMake(0, 20, SCREEN_WIDTH, ScrollView_HEIGHT);
[self.view addSubview:bV];
self.backGroundView = bV;
UIScrollView *scrollView = [[UIScrollView alloc] init];
scrollView.frame = CGRectMake(0, 0, SCREEN_WIDTH, ScrollView_HEIGHT);
scrollView.backgroundColor = [UIColor redColor];
scrollView.contentSize = CGSizeMake(SCREEN_WIDTH * IMAGEVIEW_COUNT, 0);
scrollView.pagingEnabled = YES;
scrollView.delegate = self;
scrollView.bounces = NO;
[scrollView setContentOffset:CGPointMake(SCREEN_WIDTH, 0)];
[self.backGroundView addSubview:scrollView];
self.cycleScrollView = scrollView;
}
// 給滾動圖添加圖片控件
-(void)addImageViewToScrollView {
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, ScrollView_HEIGHT)];
imageView.contentMode = UIViewContentModeScaleAspectFit;
[self.cycleScrollView addSubview:imageView];
self.leftImageView = imageView;
imageView = [[UIImageView alloc] initWithFrame:CGRectMake(SCREEN_WIDTH, 0, SCREEN_WIDTH, ScrollView_HEIGHT)];
[self.cycleScrollView addSubview:imageView];
self.midImageView = imageView;
imageView = [[UIImageView alloc] initWithFrame:CGRectMake(SCREEN_WIDTH * 2, 0, SCREEN_WIDTH, ScrollView_HEIGHT)];
[self.cycleScrollView addSubview:imageView];
self.rightImageView = imageView;
}
// 添加分頁控件
-(void)addPageControl {
int count = self.imageCount;
UIPageControl *pageC = [[UIPageControl alloc] init];
CGSize size = [pageC sizeForNumberOfPages:count];
pageC.bounds = CGRectMake(0, 0, size.width, size.height);
pageC.center = CGPointMake(SCREEN_WIDTH * 0.5, ScrollView_HEIGHT - size.height - 10);
//設(shè)置顏色
pageC.pageIndicatorTintColor=[UIColor colorWithRed:193/255.0 green:219/255.0 blue:249/255.0 alpha:1];
//設(shè)置當(dāng)前頁顏色
pageC.currentPageIndicatorTintColor=[UIColor colorWithRed:0 green:150/255.0 blue:1 alpha:1];
//設(shè)置總頁數(shù)
pageC.numberOfPages = count;
pageC.backgroundColor = [UIColor redColor];
[self.backGroundView addSubview:pageC];
self.pageC = pageC;
}
// 加載默認(rèn)圖片
-(void)setDefaultImages {
self.leftImageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"page%d.jpg",self.imageCount - 1]];
self.midImageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"page%d.jpg",0]];
self.rightImageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"page%d.jpg",1]];
self.imageIndex = 0;
[self.pageC setCurrentPage:0];
}
// 重新讀取圖片
-(void)reloadImages {
int leftImageIndex, rightImageIndex;
CGPoint currentP = [self.cycleScrollView contentOffset];
if (currentP.x < SCREEN_WIDTH) { // 滾到左邊
self.imageIndex = (self.imageIndex + self.imageCount -1) % self.imageCount;
} else if(currentP.x > SCREEN_WIDTH) { // 滾到右邊
self.imageIndex = (self.imageIndex + 1) % self.imageCount;
}
leftImageIndex = (self.imageIndex + self.imageCount -1) % self.imageCount;
rightImageIndex = (self.imageIndex + 1) % self.imageCount;
self.midImageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"page%d.jpg",self.imageIndex]];
self.leftImageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"page%d.jpg",leftImageIndex]];
self.rightImageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"page%d.jpg",rightImageIndex]];
}
#pragma mark - UIScrollViewDelegate
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (decelerate == NO) {
[self scrollViewDidEndDecelerating:scrollView];
}
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
// 滾完之后
// 重新讀取圖片
[self reloadImages];
// 滾回中間
[scrollView setContentOffset:CGPointMake(SCREEN_WIDTH, 0)];
// 設(shè)置分頁控件的序數(shù)
[self.pageC setCurrentPage:self.imageIndex];
}
@end

注意:這里只是簡單實現(xiàn)思路,沒有封裝好。