最近在作一個股票項目,項目中有這麼一個需求,利用UIScrollView左右切換去展現新聞資訊的詳情,並且資新聞資訊不少。用zaker看過新聞的都應該很熟悉這個場景了。個人第一反應就是UIScrollView的懶加載。具體思路就是整個UIScrollView中只加載三個視圖,這裏用左、中、右來區分。下面說說個人具體實現,以期拋磚引玉。程序員
在進入詳情以前,是新聞的標題展現頁,用UITableView展現的。點擊某個標題,就進入到了該條新聞的詳情頁了。注意了,你須要把該新聞是第幾條的索引值pageIndex傳過去,以便在UIScrollView進行切換新聞詳情的時候,對其進行標識。我是經過UIViewController的init方法傳過去的數組
- (instancetype)initWithWithPageIndex:(NSInteger)pageIndex withDatas:(NSArray *)newsListapp
其中,newList裏面有不少新聞的model,用於數據請求傳參。佈局
這時候新聞詳情頁裏面初始化了三條新聞,它們分別是第(pageIndex-1)條,第pageIndex條,第(pageIndex+1)條。當拖動UIScrollView向右滾動到下一頁時,將pageIndex加1。同時,將原來的三條最左邊展現新聞內容的視圖幹掉,中間和右邊的視圖則保留,還有就是把新的第(pageIndex+1)加載出來。拖動UIScrollView向右滾動查看新聞時就是這樣。那麼向左查看上一頁新聞詳情呢?與向右查看相似,當翻到上一頁時,將pageIndex減1,同時,將原來的三條最右邊展現新聞內容的視圖幹掉,保留左邊和中間的視圖,把新的第(pageIndex-1)條新聞的詳情加載出來。核心思想就是這些,先看看我寫的demo代碼吧(代碼中將新聞展現換成了圖片展現)測試
@interface CycleViewController () <UIScrollViewDelegate>atom
@property (nonatomic, strong) UIScrollView *scrollView;spa
// 傳過來的數據代理
@property (nonatomic, strong) NSArray *datas;orm
// 當前界面的索引 從0開始,最大值爲傳過來的資源數量減1索引
@property (nonatomic, assign) NSInteger pageIndex;
// 用於存放當前界面中的三個元素
@property (nonatomic, strong) NSMutableArray *tempDatas;
// 當前視圖的最右邊的偏移量,用以判斷當前視圖是否離開了屏幕(查看下一頁時)
@property (nonatomic, assign) CGFloat leftOffsetX;
// 當前視圖的最左邊的偏移量,用以判斷當前視圖是否離開了屏幕(查看上一頁時)
@property (nonatomic, assign) CGFloat rightOffsetX;
// startContentOffsetX和willEndContentOffsetX是用來判斷向左仍是向右的。這裏,我將查看下一頁定義爲向右,反之向左
@property (nonatomic, assign) CGFloat startContentOffsetX;
@property (nonatomic, assign) CGFloat willEndContentOffsetX;
@property (nonatomic, assign) BOOL isDragged;
@end
#define kScrollViewWidth CGRectGetWidth(self.scrollView.frame)
#define kScrollViewHeight CGRectGetHeight(self.scrollView.frame)
@implementation CycleViewController
- (instancetype)initWithWithPageIndex:(NSInteger)pageIndex
withDatas:(NSArray *)datas {
self = [super init];
if (self) {
self.pageIndex = pageIndex;
self.datas = datas;
_isDragged = NO;
}
return self;
}
- (NSMutableArray *)tempDatas {
if (!_tempDatas) {
_tempDatas = [NSMutableArray arrayWithCapacity:3];
}
return _tempDatas;
}
- (void)loadView {
[super loadView];
self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
self.scrollView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.scrollView.showsVerticalScrollIndicator = NO;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.delegate = self;
self.scrollView.directionalLockEnabled = YES;
self.scrollView.pagingEnabled = YES;
[self.view addSubview:self.scrollView];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
// 設置contentsize的height爲0,意爲scrollView的豎直方向不能拖動,解決scrollView的嵌套問題
self.scrollView.contentSize = CGSizeMake(kScrollViewWidth*self.datas.count, 0);
// 根據傳過來的pageIndex進行初始化
[self configImages];
// 根據傳過來的page索引值去初始化scrollView的偏移量
[self.scrollView setContentOffset:CGPointMake(kScrollViewWidth*self.pageIndex, 0)];
}
- (UIImageView *)configImageViewWithImage:(UIImage *)image withFrame:(CGRect)frame {
UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.image = image;
return imageView;
}
- (void)configImages {
UIImageView *firstImageView = nil;
UIImageView *secondImageView = nil;
UIImageView *thirdImageView = nil;
// 當圖片的數量小於三個狀況 UIScrollView把圖片都加載出來展現
if (self.datas.count <= 3) {
firstImageView = [self configImageViewWithImage:[UIImage imageNamed:[self.datas firstObject]] withFrame:CGRectMake(0, 0, kScrollViewWidth, kScrollViewHeight)];
[self.scrollView addSubview:firstImageView];
if (self.datas.count >= 2) {
secondImageView = [self configImageViewWithImage:[UIImage imageNamed:[self.datas objectAtIndex:1]] withFrame:CGRectMake(kScrollViewWidth, 0, kScrollViewWidth, kScrollViewHeight)];
[self.scrollView addSubview:secondImageView];
if (self.datas.count >= 3) {
thirdImageView = [self configImageViewWithImage:[UIImage imageNamed:[self.datas objectAtIndex:2]] withFrame:CGRectMake(kScrollViewWidth*2, 0, kScrollViewWidth, kScrollViewHeight)];
[self.scrollView addSubview:thirdImageView];
}
}
}
// 當圖片數量大於三個的狀況下 根據索引值 初始化三個視圖,而後放在scrollView上面
else {
if (self.pageIndex < 2) { // 當索引值小於2的狀況,初始化全部圖片的前三個
secondImageView = [self configImageViewWithImage:[UIImage imageNamed:[self.datas objectAtIndex:1]] withFrame:CGRectMake(kScrollViewWidth, 0, kScrollViewWidth, kScrollViewHeight)];
[self.scrollView addSubview:secondImageView];
firstImageView = [self configImageViewWithImage:[UIImage imageNamed:[self.datas firstObject]] withFrame:CGRectMake(0, 0, kScrollViewWidth, kScrollViewHeight)];
[self.scrollView addSubview:firstImageView];
thirdImageView = [self configImageViewWithImage:[UIImage imageNamed:[self.datas objectAtIndex:2]] withFrame:CGRectMake(kScrollViewWidth*2, 0, kScrollViewWidth, kScrollViewHeight)];
[self.scrollView addSubview:thirdImageView];
}
else if (self.pageIndex > self.datas.count-3) { // 初始化全部新源的最後三個
secondImageView = [self configImageViewWithImage:[UIImage imageNamed:[self.datas objectAtIndex:self.datas.count-2]] withFrame:CGRectMake(kScrollViewWidth*(self.datas.count-2), 0, kScrollViewWidth, kScrollViewHeight)];
[self.scrollView addSubview:secondImageView];
firstImageView = [self configImageViewWithImage:[UIImage imageNamed:[self.datas objectAtIndex:self.datas.count-3]] withFrame:CGRectMake(kScrollViewWidth*(self.datas.count-3), 0, kScrollViewWidth, kScrollViewHeight)];
[self.scrollView addSubview:firstImageView];
thirdImageView = [self configImageViewWithImage:[UIImage imageNamed:[self.datas objectAtIndex:self.datas.count-1]] withFrame:CGRectMake(kScrollViewWidth*(self.datas.count-1), 0, kScrollViewWidth, kScrollViewHeight)];
[self.scrollView addSubview:thirdImageView];
}
else { // 初始化索引值及其全部的圖片內容
secondImageView = [self configImageViewWithImage:[UIImage imageNamed:[self.datas objectAtIndex:self.pageIndex]] withFrame:CGRectMake(kScrollViewWidth*self.pageIndex, 0, kScrollViewWidth, kScrollViewHeight)];
[self.scrollView addSubview:secondImageView];
firstImageView = [self configImageViewWithImage:[UIImage imageNamed:[self.datas objectAtIndex:self.pageIndex-1]] withFrame:CGRectMake(kScrollViewWidth*(self.pageIndex-1), 0, kScrollViewWidth, kScrollViewHeight)];
[self.scrollView addSubview:firstImageView];
thirdImageView = [self configImageViewWithImage:[UIImage imageNamed:[self.datas objectAtIndex:self.pageIndex+1]] withFrame:CGRectMake(kScrollViewWidth*(self.pageIndex+1), 0, kScrollViewWidth, kScrollViewHeight)];
[self.scrollView addSubview:thirdImageView];
}
}
// 這裏添加的順序要注意,不是隨便添加的
[self.tempDatas addObject:firstImageView];
if (secondImageView != nil) {
[self.tempDatas addObject:secondImageView];
}
if (thirdImageView != nil) {
[self.tempDatas addObject:thirdImageView];
}
self.leftOffsetX = kScrollViewWidth*(self.pageIndex+1);
self.rightOffsetX = kScrollViewWidth*self.pageIndex;
// 剛開始點擊了第幾張
self.title = [NSString stringWithFormat:@"第 %ld 張", self.pageIndex+1];
}
#pragma mark == UIScrollViewDelegate ==
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
self.isDragged = YES;
// 拖拽圖片的起始偏移量
self.startContentOffsetX = scrollView.contentOffset.x;
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
// 將要中止拖拽時 scrollView偏移的位置
self.willEndContentOffsetX = scrollView.contentOffset.x;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (self.isDragged) {
BOOL directionRight;
CGFloat currentOffsetX = scrollView.contentOffset.x;
if (currentOffsetX > self.willEndContentOffsetX && self.willEndContentOffsetX > self.startContentOffsetX) {
directionRight = YES; // scrollView向右偏移
// 當前是第幾張
int currentPage = ceil(scrollView.contentOffset.x/CGRectGetWidth(scrollView.frame))+1;
if (currentPage <= self.datas.count) {
self.title = [NSString stringWithFormat:@"第 %d 張", currentPage];
}
}
else if (currentOffsetX < self.willEndContentOffsetX && self.willEndContentOffsetX < self.startContentOffsetX) {
directionRight = NO; // scrollView向左偏移
// 當前是第幾張
int currentPage = ceil(scrollView.contentOffset.x/CGRectGetWidth(scrollView.frame))+1;
if (currentPage > 0) {
self.title = [NSString stringWithFormat:@"第 %d 張", currentPage];
}
}
else {
return; // 須要嚴格的條件判斷 當滑動的幅度較小時,則不進行股票的預加載
}
if (directionRight) {
if (self.leftOffsetX <= scrollView.contentOffset.x) { // 當前視圖已經從界面中移除出去
self.pageIndex++;
if (self.pageIndex >= self.datas.count-1) {
self.pageIndex = self.datas.count-1;
self.leftOffsetX = CGRectGetWidth(scrollView.frame)*self.datas.count;
self.rightOffsetX = CGRectGetWidth(scrollView.frame)*(self.datas.count-1);
return;
}
self.leftOffsetX = CGRectGetWidth(scrollView.frame)*self.pageIndex;
self.rightOffsetX = CGRectGetWidth(scrollView.frame)*(self.pageIndex-1);
}
}
else {
if (self.rightOffsetX >= scrollView.contentOffset.x) { // 當前視圖已經從界面中移除出去
self.pageIndex--;
if (self.pageIndex <= 0) {
self.pageIndex = 0;
self.leftOffsetX = CGRectGetWidth(scrollView.frame);
self.rightOffsetX = 0;
return;
}
self.leftOffsetX = CGRectGetWidth(scrollView.frame)*self.pageIndex;
self.rightOffsetX = CGRectGetWidth(scrollView.frame)*(self.pageIndex-1);
}
}
}
}
// 這個方法裏面 才真正進行視圖內容的加載
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
int currentPage = ceil(scrollView.contentOffset.x/CGRectGetWidth(scrollView.frame))+1;
self.title = [NSString stringWithFormat:@"第 %d 張", currentPage];
if (self.isDragged) {
// 此處爲容錯判斷 若滑動過快 將會致使currentPage與self.pageIndex不一致 此時 將scrollView上原來的視圖刪除掉,而後再進行從新佈局
if (self.datas.count > 3) {
NSInteger currentPage = ceil(scrollView.contentOffset.x/CGRectGetWidth(scrollView.frame));
if (currentPage != self.pageIndex) {
self.pageIndex = currentPage;
if (self.pageIndex >= self.datas.count-1) {
self.pageIndex = self.datas.count-1;
}
if (self.pageIndex <= 0) {
self.pageIndex = 0;
}
for (UIImageView *imageView in self.tempDatas) {
[imageView removeFromSuperview];
}
[self.tempDatas removeAllObjects];
[self configImages];
return;
}
}
BOOL directionRight;
CGFloat endOffsetX = scrollView.contentOffset.x;
if (endOffsetX > self.willEndContentOffsetX && self.willEndContentOffsetX > self.startContentOffsetX) {
directionRight = YES;
}
else if (endOffsetX < self.willEndContentOffsetX && self.willEndContentOffsetX < self.startContentOffsetX) {
directionRight = NO;
}
else {
// 容錯檢查 當滑動到左邊或右邊的的邊界時,若顯示了空白頁,剛從新進行佈局,加載最左邊或最右邊的三支股票
if (self.datas.count > 3) { // 在資源的數量小於3個的狀況因所有加載了內容,因此不須要這種容錯檢查(下同)
UIImageView *imageView = nil;
if (scrollView.contentOffset.x <= 0) {
imageView = [self.tempDatas objectAtIndex:0]; // 最左邊
}
else if (scrollView.contentOffset.x+CGRectGetWidth(scrollView.frame) >= CGRectGetWidth(scrollView.frame)*self.datas.count) { // 最右邊
imageView = [self.tempDatas objectAtIndex:2];
}
else {
imageView = [self.tempDatas objectAtIndex:1];
}
if (imageView.frame.origin.x != CGRectGetWidth(scrollView.frame)*self.pageIndex) {
for (UIImageView *imageView in self.tempDatas) {
[imageView removeFromSuperview];
}
[self.tempDatas removeAllObjects];
[self configImages];
}
}
return; // 須要嚴格的條件判斷 當滑動的幅度較小時,則不進行股票的預加載
}
// 容錯檢查 若滑動較快,停在了中間某個位置時,若顯示空白,則替換掉self.tempDatas中舊的視圖控制器,從新進行佈局
if (self.datas.count > 3) {
UIImageView *imageView = nil;
if (directionRight) {
if (self.pageIndex == 1) { // 向右滑動, self.pageIndex爲1的狀況下進行檢查
imageView = [self.tempDatas objectAtIndex:1];
}
else {
imageView = [self.tempDatas objectAtIndex:2];
}
}
else {
if (self.pageIndex == self.datas.count - 2) { // 向左滑動,self.pageIndex爲self.dataList.count-2的狀況下進行容錯檢查
imageView = [self.tempDatas objectAtIndex:1];
}
else {
imageView = [self.tempDatas objectAtIndex:0];
}
}
if (imageView.frame.origin.x != CGRectGetWidth(scrollView.frame)*self.pageIndex) {
for (UIImageView *imageView in self.tempDatas) {
[imageView removeFromSuperview];
}
[self.tempDatas removeAllObjects];
[self configImages];
}
}
if (directionRight) {
// 這裏 將leftOffsetX還原,以不影響視圖的加載
self.leftOffsetX -= CGRectGetWidth(scrollView.frame);
self.rightOffsetX -= CGRectGetWidth(scrollView.frame);
if (self.leftOffsetX <= scrollView.contentOffset.x) {
if (self.pageIndex >= self.datas.count-1) {
self.pageIndex = self.datas.count-1;
UIImageView *rightImageView = [self.tempDatas lastObject];
self.leftOffsetX = rightImageView.frame.origin.x+CGRectGetWidth(rightImageView.frame);
self.rightOffsetX = rightImageView.frame.origin.x;
return;
}
if (self.pageIndex == 1) { // 翻到最左邊 而後回翻時 此種狀況數組中的三個VC不動
UIImageView *middleImageView = [self.tempDatas objectAtIndex:1];
self.leftOffsetX = middleImageView.frame.origin.x + CGRectGetWidth(middleImageView.frame);
self.rightOffsetX = middleImageView.frame.origin.x;
return;
}
UIImageView *firstImageView = [self.tempDatas firstObject];
[firstImageView removeFromSuperview];
UIImageView *thirdImageView = [self configImageViewWithImage:[UIImage imageNamed:[self.datas objectAtIndex:self.pageIndex+1]] withFrame:CGRectMake(kScrollViewWidth*(self.pageIndex+1), 0, kScrollViewWidth, kScrollViewHeight)];
[self.scrollView addSubview:thirdImageView];
[self.tempDatas addObject:thirdImageView];
[self.tempDatas removeObjectAtIndex:0];
UIImageView *secondImageView = [self.tempDatas objectAtIndex:1];
self.leftOffsetX = secondImageView.frame.origin.x + CGRectGetWidth(secondImageView.frame);
self.rightOffsetX = secondImageView.frame.origin.x;
}
}
else {
// 這裏 將leftOffsetX還原 以正常加載視圖
self.leftOffsetX += CGRectGetWidth(scrollView.frame);
self.rightOffsetX += CGRectGetWidth(scrollView.frame);
if (self.rightOffsetX >= scrollView.contentOffset.x) {
if (self.pageIndex <= 0) {
self.pageIndex = 0;
UIImageView *firstImageView = [self.tempDatas firstObject];
self.leftOffsetX = firstImageView.frame.origin.x+CGRectGetWidth(firstImageView.frame);
self.rightOffsetX = firstImageView.frame.origin.x;
return;
}
if (self.pageIndex == self.datas.count - 2) { // 翻到最右邊 回翻 翻到倒數第二個的時候 數組中的三個VC保持不變
UIImageView *secondImageView = [self.tempDatas objectAtIndex:1];
self.leftOffsetX = secondImageView.frame.origin.x + CGRectGetWidth(secondImageView.frame);
self.rightOffsetX = secondImageView.frame.origin.x;
return;
}
UIImageView *rightImageView = [self.tempDatas lastObject];
[rightImageView removeFromSuperview];
UIImageView *firstImageView = [self configImageViewWithImage:[UIImage imageNamed:[self.datas objectAtIndex:self.pageIndex-1]] withFrame:CGRectMake(kScrollViewWidth*(self.pageIndex-1), 0, kScrollViewWidth, kScrollViewHeight)];
[self.scrollView addSubview:firstImageView];
[self.tempDatas removeLastObject];
[self.tempDatas insertObject:firstImageView atIndex:0];
UIImageView *secondImageView = [self.tempDatas objectAtIndex:1];
self.leftOffsetX = secondImageView.frame.origin.x + CGRectGetWidth(secondImageView.frame);
self.rightOffsetX = secondImageView.frame.origin.x;
}
}
}
}
暈了嗎?若是沒暈,說明你是一個能hold得住的老程序員了。所謂臺上十分鐘,臺下十年功。程序員就是一個修煉的過程。
不過怎麼會有那麼一大坨的代碼?別急,容我再敘。
一開始,我出沒寫那麼多,而後寫出來以後在真機上我也愉快的體驗了一下,挺好,測試測了一下,也挺好。你好我好你們好,而後我就玩去了。話說要提交新版本前的那個晚上,一切都那麼和諧,只等打包上傳而後回家過週末了!好happy啊!
可是,測試的手一抖,而後,就加班到了凌晨後,容我作一個悲傷的表情!
事情是這樣的,他左右拖動新聞的速度比較快,而後,界面就亂了!當時我就鬱悶了,爲何?一看,由於我將視圖的加載放到了UIScrollView的
- (void)scrollViewDidScroll:(UIScrollView *)scrollView方法中了,當左右拖動較快時,屏幕就亂了。
這個時候,我就想了,在你左右拖動較快的狀況下(極可能是無聊瞎劃拉),就去給你加載,而你又不看,不是太傻了嗎!那好吧,就懶到底吧,把加載放到scrollView停下來的代理方法裏面
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
還有,就是要處理數組越界的狀況,容錯處理,上面代碼裏面我註釋了。
初來乍到,寫的比較亂,望多多指教!