利用RAC一句話實現上拉刷新下拉刷新
最近在研究上拉刷新下拉刷新,有點小心得和大家分享下。
首先我是先在網上搜博客看,發現要不就是用原生UIRefreshControl要不就是第三方庫的教程,這都不是我想要的(好的開源庫推薦 MJRefresh )。
于是我開始看大神們的源代碼,了解了下原理。
刷新原理
首先上拉下拉刷新肯定是基于UIScrollView的基礎上的,包括UITableView其實也是一個UIScrollView。而實現上拉下拉刷新的原理就是UIScrollView中的代理方法
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;// any offset changes
這個方法是在你的scrollView滾動時就會執行的方法。
這里有必要說明下scrollView的兩個屬性,一個是contentSize,一個是contentOffset。這里上圖片講解可能直觀一點。
PS:不會用mac的畫圖工具比較丑見諒。
看圖,這里的綠色框就是我們的手機屏幕,我們的UI都是呈現在手機屏幕上的,那么黑色的框就是contentSize。就是說,雖然手機屏幕只有一點大,但是我們的scrollView并不是只有一點大的,這個屬性是可以設置的,而我們滾動scrollView其實就是滾動黑色框,這樣看到的界面就會不一樣了。而圖上標注的紅點就是contentOffset。contentOffset是一個CGPoint,代表當前屏幕所在位置左上角相對于scrollView.contentSize左上角的橫縱坐標值。
了解了scrollView我們就來說刷新。在 - (void)scrollViewDidScroll:(UIScrollView *)scrollView;// any offset changes 方法中我們應該監聽contentOffset的值,如果他的縱坐標到某個點以上我們就執行刷新數據,移動到某個點以下我們就執行加載數據。具體看代碼可能更好理解。
- (void)scrollViewDidScroll:(UIScrollView *)scrollView { if ([scrollView isEqual:_tableView]) { if (scrollView.contentOffset.y < -50 ) { //下拉刷新方法 } if (scrollView.contentOffset.y > 800 ) { //上拉加載方法 } } }
原理很簡單,但是實現的話還是有很多細節需要考慮的,比如 scrollView.contentOffset.y < -50 的情況是很多的,當用戶快速上拉到-100時,可能在-51執行一次刷新方法,在-52執行一次,-53執行一次。。。。。。這肯定是不合理的。
所以我們需要節流。這里提兩個我的個人觀點,一個是開線程執行刷新辦法,如果線程存在不會新開一個,這樣可以保證刷新方法執行一次。還有一種是用KVO監聽用戶手指松開的動作,松開的時候再刷新,或者 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate 用這個代理方法,他會幫你監聽手指是否松開。這兩個方法我都沒試過,大家可以自己嘗試下,有錯希望反饋給我,共同進步。
但是如果僅僅是判定松開我覺得是滿足不了需求的。我現在希望就是當用戶快下滑到底部時就自動加載新數據,那我應該怎么實現呢?總不能用戶一拉到底不松手就不加載吧。
RAC一個方法實現刷新
我想到了RAC。這個想法可能有點非主流,所以肯定有邏輯考慮不周的地方,希望各位指出。這里我先宏定義了下,為了縮短代碼
#define VIEWHEIGHT self.view.frame.size.height
然后就是RAC了。
[[[RACObserve(self.tableView, contentOffset) map:^id(id value) { if (self.tableView.contentOffset.y < -50) { return @"1"; } if (self.tableView.contentOffset.y > self.tableView.contentSize.height - VIEWHEIGHT * 1.5 && self.tableView.contentSize.height - VIEWHEIGHT * 1.5 > 0) { return @"2"; }else{ return @"0"; } }] distinctUntilChanged] subscribeNext:^(id x) { debugLog(@"%@", x); if ([x integerValue] == 1) { [self netWork]; }else if ([x integerValue] == 2){ [self loadMoreData]; } }];
這里是用了RAC KVO的寫法,不會的可以點我文章復習下。首先寫了一個通知監聽tableView的contentOffset,如果發生變化立刻進入map產生的映射中執行map中的方法。我給情況分了類,如果用戶下拉,返回1,如果上拉快到底部時返回2。并且在映射完成后用了 distinctUntilChanged 屬性,當我的映射值不產生變化時是不會傳遞映射值的。這樣當用戶拉倒需要刷新的位置,只會發一個信號給訂閱者,只會執行一次刷新數據的方法,這樣我所有的需求就迎刃而解了。
如果有更好的辦法希望大家給我指出,謝謝大家。