回头看UITableView(三)-下拉刷新的实现

最近陷入了项目中一个日历月视图与周视图切换效果的实现,长时间没有实现想要的效果,沮丧至极。烦请有好想法的同学指点一二,在线等@留什么白


结束例行的啰嗦,进入正题。

大家可能都用过MJRefresh,十分之方便,李明杰老师借助runtime的特性,极尽之能,让千千万万小白开发者和遵循不重复制造轮子原则的开发者用最少的代码就实现了界面下拉刷新的功能。我们今天不谈runtime的黑魔法,而是UITableView最常见的刷新数据方式下拉刷新的实现过程。

首先,我们定义三个枚举值

typedef NS_ENUM(NSUInteger, RefreshState) {

    RefreshStateNormal,//正常
    RefreshStatePulling,//释放即可刷新
    RefreshStateLoading,//加载中
};
分别表示正常状态、释放即可刷新状态、加载中状态

为了简单说明,我们只用一个UILabel来表现就可以,把它放在内容上方,也就是正常情况下看不到的地方,只有下拉的时候才能看到。

UILabel的位置

然后我们估算一个大体的距离,从而确定触发加载状态的临界点。

简单描述一下将要实现的情形:

我们往下滑动视图,如果视图下滑到触发点,状态变为RefreshStatePulling,即标签文字变为“松手即可刷新”。
接下来有两种状况会发生:一,松开手;二,不松手,又向初始位置滑。第一种情况,当你松开手时,因为UIScrollView有回弹效果,视图会往上滑动,当到达触发点时,状态改为RefreshStateLoading,即视图停在当前位置,不受回弹作用影响,标签文字变为“加载中...”,等加载动作完成之后,手动触发复原动作,视图滑到原始状态 RefreshStateNormal,同时标签文字变为‘下拉刷新’;第二种情况,你没有松手,然后又往回滑动,当到达触发点以上时,状态变为初始状态RefreshStateNormal

如果你对上述过程不是很了解的话,那你随便找个带下拉刷新的应用试试就行了,不用温柔,越暴力越好。弄清上述过程是实现整个流程的基础。

接下来要做的就是决定什么时候改变状态了,参照上述过程,我们在UIScrollView的代理函数scrollViewDidScroll:监控contentOffset的变化,再结合isDragging属性就可以改变状态了。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{

    if(scrollView.contentOffset.y < -SwitchPoint-self.originalInsetTop){
        if(self.refreshState == RefreshStateNormal){//小于临界值(在触发点以下),如果状态是正常就转为下拉刷新,如果正在刷新或者已经是下拉刷新则不变
            self.refreshState = RefreshStatePulling;
        }
    }else{//大于临界值(在触发点以上,包括触发点)
        if(scrollView.isDragging){//手指没有离开屏幕
            if(self.refreshState == RefreshStatePulling){//原来是下拉的话变成正常,原来是刷新或者正常的话不变
                self.refreshState = RefreshStateNormal;
            }
        }else{//手指离开屏幕
            if(self.refreshState == RefreshStatePulling){//原来是下拉的话变成加载中,原来是加载中或者正常的话不变
                self.tableView.contentInset = UIEdgeInsetsMake(self.originalInsetTop+SwitchPoint, 0, 0, 0);//改变contentInset的值就可以取消回弹效果停留在当前位置了 关于contentIinset的介绍,可以查看我的上一篇文章
                self.refreshState = RefreshStateLoading;
            }
        }
    }    
}

这段代码决定了UIScrollView每个时刻的状态,那么接下来的就简单多了,只要重写一下setRefreshState:方法就可以了。

- (void)setRefreshState:(RefreshState)refreshState{

    _refreshState = refreshState;
    switch (refreshState) {
        case RefreshStateNormal:
            self.pulldownLabel.text = @"下拉刷新";
            [self.pulldownLabel sizeToFit];
            break;
        case RefreshStateLoading:
            self.pulldownLabel.text = @"正在刷新...";
            [self.pulldownLabel sizeToFit];
            if(self.refreshBlock){
                self.refreshBlock();//这里就是你要执行的耗时的操作
            }            
            break;
        case RefreshStatePulling:
            self.pulldownLabel.text = @"松开即可刷新";
            [self.pulldownLabel sizeToFit];
            break;
        default:
            break;
    }
}

看一下我们的效果:
平凡而简单的效果
当然你会说这不够diao,不够炫酷,人家的效果都是这样:

又被你发现了,😂,我满(hao)是(wu)虔(jie)诚(cao)地盗了大神的图,@M了个J,大神,膝盖已发货,请验收。

那么我们怎么实现这样的效果呢,很简单,在上边设置状态的代码里添加动画就行了。来一段伪的代码:

case RefreshStateNormal:

         self.pulldownLabel.text = @"下拉刷新";
        [UIView animateWithDuration:0.3 animations:^{
        self.arrowImage.transform = CGAffineTransformMakeRotation(M_PI/2);//箭头旋转180º
    }];
        break;

还有一点需要强调,在scrollViewDidScroll:里保持加载状态时修改了contentInset,所以取消加载状态恢复原状时只需...如此如此:
- (void)endRefresh{
    if(self.refreshState == RefreshStateLoading){
        self.refreshState = RefreshStateNormal;
        [UIView animateWithDuration:0.3 animations:^{
            self.tableView.contentInset = UIEdgeInsetsMake(self.originalInsetTop, 0, 0, 0);
        } completion:nil];
    }
}

OK,希望看到这里你能理解下拉刷新的整个过程及原理了,欢迎指正和打击。


标题:回头看UITableView(三)-下拉刷新的实现
作者:yuyedaidao
地址:http://mooncake.wang/articles/2018/11/09/1573699412871.html