最近在研究上拉刷新下拉刷新,有点小心得和大家分享下。
首先我是先在网上搜博客看,发现要不就是用原生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。这个想法可能有点非主流,所以肯定有逻辑考虑不周的地方,希望各位指出。这里我先宏定义了下,为了缩短代码
#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
属性,当我的映射值不产生变化时是不会传递映射值的。这样当用户拉倒需要刷新的位置,只会发一个信号给订阅者,只会执行一次刷新数据的方法,这样我所有的需求就迎刃而解了。
如果有更好的办法希望大家给我指出,谢谢大家。