UITableView:下拉刷新和上拉加載更多


【轉載請注明出處】

 

本文將說明讓UIScrollView支持"下拉刷新"和"上拉加載更多"的實現機制,並實現一個可用的tableView子類,以下主要以"下拉刷新"進行說明。

工程地址在帖子最下方,只需要代碼的直拉到底即可。

 

目錄

1、contentInset和下拉刷新;

2、動畫、動態文字和刷新時間;

3、其他;

4、工程地址。 

 

【added at 2013.11.28】

下拉刷新和section headerView沖突原因分析及解決辦法。 

 

1、contentInset和下拉刷新

contentInset是UIScrollView的屬性,它描述了UIScrollView的內容View的內邊距,具體可見官方文檔:

Scroll View programming Guide for iOS

目前幾乎所有"下拉刷新"的第三方庫都是依賴它實現的。

【為便於討論,將下拉刷新/上拉加載時顯示的視圖稱為refresh panel,如下圖】

 

 

在用戶手指向下滑動到最終更新界面的過程中,經歷了4個步驟:

(1)隨着用戶下拉逐漸顯示UITableView頂部的refresh panel;

(2a)下拉達到預設位置,狀態文字變為"松開可以刷新";

(2b)下拉未達到預設位置,用戶手指離開屏幕,ScrollView彈回,refresh panel重新隱藏起來,結束。

(3)用戶手指離開屏幕,refresh panel保持顯示。狀態文字變為"加載中",在后台執行更新數據的操作;

(4)數據更新完成,返回主線程,重新隱藏refresh panel,結束。

 

可以看到,如果不考慮刷新時間、狀態文字等,實現"下拉刷新"實際上只需要做到2件事:

(1)隱藏refresh panel(初始時和刷新后)

隱藏refresh panel,即使其居於UITableView的上方且不可見,如下

 1 - (void)addDragHeaderView
 2 {
 3     if (self.shouldShowDragHeader && !dragHeaderView)
 4     {
 5         CGRect frame = CGRectMake(0, -self.dragHeaderHeight,
 6                                     self.bounds.size.width, self.dragHeaderHeight);
 7         dragHeaderView = [[Pull2RefreshView alloc]
 8                                     initWithFrame:frame type:kPull2RefreshViewTypeHeader];
 9         [self addSubview:dragHeaderView];
10     }
11 }

注意:不應使用UITableView的tableHeaderView來作為refresh panel,一來會使得下拉刷新和自定義tableHeaderView無法共存,二來UITableView的內容視圖是包含tableHeaderView的,即

tableView.contentSize.height == tableView.tableHeaderView.height

                                          + n * sectionHeaderView.height

                                          + m * cell.height

                                          + tableView.tableFooterView.height

因此想讓tableHeaderView默認不可見,需要修改contentOffset的初始值並在用戶滑動時控制滑動范圍,比較麻煩。】

(2)顯示refresh panel

當用戶手指下拉達到預設值並離開屏幕,立即修改contentInset,使refresh panel保持顯示,如下

1 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
2 {
3         if (scrollView.contentOffset.y < -self.dragHeaderHeight - 10.0f
4         {
5             //使refresh panel保持顯示
6             self.contentInset = UIEdgeInsetsMake(self.dragHeaderHeight, 0, 0, 0);
7         }
8 }

如此,更新數據后再次隱藏refresh panel的方式也很明了

1 self.contentInset = UIEdgeInsetsZero;

 

2、動畫、動態文字和刷新時間

一個標准的refresh panel(如新浪微博的),包含指示箭頭、加載菊花、狀態文字和更新時間四部分,如下

 1 @implementation Pull2RefreshView
 2 {
 3     UILabel     *hintLabel;
 4     UILabel     *timeLabel;
 5     
 6     UIImageView             *arrowImageView;
 7     UIActivityIndicatorView *indicatorView;
 8     
 9     Pull2RefreshViewType refreshType;
10 }

在1中已經做到了refresh panel的顯示的隱藏,2中只需要在合適的時候改變refresh panel的顯示內容即可。

(1)"下拉可以刷新"—>"松開立即更新"

在UIScrollView的委托函數scrollViewDidScroll:中檢測用戶下拉的程度,達到預設值后就改變狀態,如下:

 1 - (void)scrollViewDidScroll:(UIScrollView *)scrollView
 2 {
 3     //拉動足夠距離,狀態變更為“松開....”
 4     if (self.shouldShowDragHeader && dragHeaderView)
 5     {
 6         if (dragHeaderView.state == kPull2RefreshViewStateDragToRefresh
 7             && scrollView.contentOffset.y < -self.dragHeaderHeight - 10.f
 8             && !headerRefreshing
 9             && !footerRefreshing)
10         {
11             [dragHeaderView flipImageAnimated:YES];
12             [dragHeaderView setState:kPull2RefreshViewStateLooseToRefresh];
13         }
14     }
15 }

修改指示箭頭方向為向上,在setState中修改狀態文本為"松開立即刷新"。

(2)"松開立即刷新"—>"加載中..."
在UIScrollView的委托函數scrollViewDidEndDragging: willDecelerate:中檢測用戶手指離開屏幕時的情況:

 1 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
 2 {
 3         //拉動足夠距離,松開后,狀態變更為“加載中...”
 4         if (dragHeaderView.state == kPull2RefreshViewStateLooseToRefresh
 5             && scrollView.contentOffset.y < -self.dragHeaderHeight - 10.0f)
 6         {
 7             //使refresh panel保持顯示
 8             self.contentInset = UIEdgeInsetsMake(self.dragHeaderHeight, 0, 0, 0);
 9             [dragHeaderView flipImageAnimated:YES];
10             [dragHeaderView setState:kPull2RefreshViewStateRefreshing];
11         }
12 }

相比1中添加了修改指示箭頭方向,在setState中修改狀態文本為"加載中..."。

(3)"加載中..."—>"下拉可以刷新"
這一步需要由外部(通常是ViewController)判斷何時執行,提供一個方法供外部調用,如下:

 1 - (void)completeDragRefresh
 2 {
 3         [UIView beginAnimations:nil context:NULL];
 4         [UIView setAnimationDuration:0.3f];
 5         self.contentInset = UIEdgeInsetsZero;
 6         [UIView commitAnimations];
 7         
 8         [dragView flipImageAnimated:NO];
 9         [dragView setState:kPull2RefreshViewStateDragToRefresh];
10 }

指示箭頭方向和狀態文本恢復為初始狀態,更新時間變為當前時間。

 

3、其他
(1)"下拉刷新"和"上拉加載更多"的不同
"下拉刷新"的refresh panel的位置始終不變,而"上拉加載更多"的refresh panel則需要隨着tableView.contentSize的變化而變化。一個比較簡單的方案是:

1 tableView.tableFooterView = dragFooterView;

在某些第三方實現中便是如此處理的,好處是簡單到只需要一行代碼,壞處是tableFooterView被占用了。考慮到tableFooterView在"上拉加載更多"的情境下不太需要自定義,影響不大。

另一個方案是在初始化時和數據更新后,設置refresh panel的frame使其始終保持正確位置。

demo中用了第1種方法,SVPullToRefresh則采用了第2種方法。

此外,在觸發刷新的條件上,二者也是不同的。"下拉刷新"時,為防止刷新"太過靈敏",需要設置一個閥值來控制,所以才有"松開立即刷新"。而"上拉加載更多"是在用戶往下不斷瀏覽內容的過程中觸發的,因此只需滑動到內容底部就立即觸發加載。

 

(2)如何封裝

UITableView作為派生類,是和基類UIScrollView共享一個delegate屬性的,即UITableViewDeleagte和UIScrollViewDelegate是同時指定的。這帶來的問題是,想要封裝一個支持"下拉刷新"和"上拉加載更多"的UITableView子類,恐怕不得不增加一層委托,將UITableViewDelegate中的各種方法都轉到外部進行實現,如

1 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
2 {
3     if (self.pullDelegate && [self.pullDelegate respondsToSelector:@selector(pull2RefreshTableView:didSelectRowAtIndexPath:)])
4     {
5         [self.pullDelegate pull2RefreshTableView:self didSelectRowAtIndexPath:indexPath];
6     }
7 }

可謂麻煩至極。

Added 2014.7.24:SVPullToRefresh中,提供了一種很好的思路,通過KVO監視UIScrollView的contentOffset.y的變化,來判斷是否會觸發下拉刷新或上拉加載,有興趣的可以直接看它的源碼:

https://github.com/samvermette/SVPullToRefresh

  

(3)下拉刷新和UITableView的section headerView沖突

 由於內容較多,單獨開一帖進行說明,地址:

 http://www.cnblogs.com/lexingyu/p/3448532.html

4中工程已進行相應修改。 

 

 

4、封裝的Pull2RefreshTableView Demo工程

使用iOS 6.1 SDK編譯,使用ARC。 

地址:https://github.com/cDigger/CDPullToRefreshDemo

 

【參考】

1、Scroll View Programming Guide for iOS

https://developer.apple.com/library/ios/documentation/windowsviews/conceptual/UIScrollView_pg/CreatingBasicScrollViews/CreatingBasicScrollViews.html

2、SVPullToRefresh

https://github.com/samvermette/SVPullToRefresh

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM