美文网首页
基于MJRefresh的列表加载刷新逻辑的封装

基于MJRefresh的列表加载刷新逻辑的封装

作者: 樂幽 | 来源:发表于2023-08-22 17:08 被阅读0次

    MJRefresh大家都不陌生,我们平时在开发的时候遇到下拉刷新上拉加载的需求,大多时候都会用到它。

    而它的使用也比较简单:

    _collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
        //请求第一页数据
    }];
    _collectionView.mj_footer = [MJRefreshAutoFooter footerWithRefreshingBlock:^{
        //请求其他页数据
    }];
    [_collectionView.mj_header beginRefreshing];
    

    RefreshingBlock里边,我们需要进行网络请求,也需要在请求结束后,结束刷新状态,并刷新列表视图。大致代码如下:

    - (void)refresh{
        XXXListRequest *request = [[XXXListRequest alloc] init];
        [request startRequestWithSuccessBlock:^(XXXListResult *responseData) {
            //结束刷新
            [_collectionView.mj_header endRefreshing];
            //刷新列表
            [_collectionView reloadData];
        } failureBlock:^(NSError *error) {
            [_collectionView.mj_header endRefreshing];
            //show fail toast
        }];
    }
    

    看到这里,大家可能有些疑问,既然这么简单,那有什么好封装的呢?

    其实不然,看似简单的逻辑,可能也会有很多细节需要注意~

    下面我们来分析下,有哪些隐藏的逻辑在里边。

    PageSize,PageIndex逻辑


    讲到加载和刷新,就离不开PageSizePageIndex,后端给我们的接口一定会包含这两个参数,为了处理相关逻辑,我们需要在控制器里增加对应的两个属性,然后在合适的地方给pageSize赋值,比如viewDidLoad方法中。

    这里有一点需要注意的是,PageIndex的初始值是后端定的,所以不一定是0,也有可能是其他值。

    Total逻辑


    还有一个容易忽略的就是Total逻辑,类似的接口返回值中一般都会带个Total字段,表示后台数据总量。

    我们可以用这个值来判断,是否还有数据可以加载,当然,并不是所有的后端都有返回这么一个字段的习惯,如果没有这个字段,我们还可以根据返回的元素数量是否等于PageSize来判断,只是这种判断方法稍差于用Total判断,当某一次请求返回的元素数量等于PageSize,而此时后台恰好也没有更多数据的时候,Total判断法直接就可以进行判断,而计算size法需要在下次加载结束后才能判断。

    当没有更多数据的时候,我们需要对用户进行提示,常用的提示方法一般有两种:

    1. refreshFooter的状态置为NoMoreData,并根据需要设置显示的文案。
    2. 直接将refreshFooter移除。

    如果不添加这个逻辑,没有更多数据的时候,用户依然可以进行上拉加载操作,从而触发没有意义的网络请求。

    数据源


    请求回来的数据,需要添加到一个DataArray中,然后,需要设置列表视图的DataSource,需要根据DataArray中的数据来刷新列表视图。

    优化后的代码


    接下来,我们给之前的代码添加上述的相关逻辑,假设PageIndex的初始值是1,没有更多数据后直接移除footer,修改后的代码大致如下:

    @interface CollectionViewController (){
        __weak IBOutlet UICollectionView *_collectionView;
    }
    @property (assign, nonatomic) NSInteger pageIndex;
    @property (assign, nonatomic) NSInteger pageSize;
    
    @property (copy, nonatomic) NSMutableArray *dataList;
    @end
    
    @implementation CollectionViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        _pageSize = 5;
        _collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
            //请求第一页数据
            [self refresh];
        }];
        [_collectionView.mj_header beginRefreshing];
    }
    
    - (void)refresh{
        _pageIndex = 1;
        [self loadMoreData];
    }
    
    - (void)loadMoreData{
        XXXListRequest *request = [[XXXListRequest alloc] init];
        request.pageIndex = _pageIndex;
        request.pageSize = 5;
        [request startRequestWithSuccessBlock:^(XXXListResult *responseData) {
            if (_pageIndex == 1) {
                self.dataList = [NSMutableArray arrayWithArray:list];
                [_collectionView.mj_header endRefreshing];
                if (!_collectionView.mj_footer && self.dataList.count < responseData.total) {
                    _collectionView.mj_footer = [MJRefreshAutoFooter footerWithRefreshingBlock:^{
                        //请求其他页数据
                        [self loadMoreData];
                    }];
                }
            }else{
                [self.dataList addObject:responseData.list];
                if (self.dataList.count < responseData.total) {
                    [_collectionView.mj_footer endRefreshing];
                }else{
                    _collectionView.mj_footer = nil;
                }
            }
            _pageIndex ++;
            //刷新列表
            [_collectionView reloadData];
        } failureBlock:^(NSError *error) {
            if (_pageIndex == 1) {
                [_collectionView.mj_header endRefreshing];
            }else{
                [_collectionView.mj_footer endRefreshing];
            }
            //show fail toast
        }];
    }
    
    #pragma mark - UICollectionViewDataSource
    
    - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
        return self.dataList.count;
    }
    
    - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
        CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
        cell.textLabel.text = self.dataList[indexPath.row];
        return cell;
    }
    
    @end
    

    这里有个细节需要注意,只有在第一屏数据加载完毕之后,我们才能知道,是否还有更多的数据,所以,我们没必要在最开始的时候就把footer加上。我们可以在第一屏加载完毕之后进行判断,如果还有更多数据,再添加footer

    到此为止,列表加载刷新的常用逻辑就添加完毕了~虽然东西确实不多,但每次遇到类似的逻辑的时候,都要写很多重复的逻辑,确实很麻烦。

    LSYListViewDataSource


    为了提高效率,降低代码复杂度,我将这些逻辑全部都封装到了一个单独的类当中,将这个类作为列表视图的DataSource,只需要有一些简单的设置,即可完成上边的所有工作,大致调用如下:

    __weak typeof(self) weakSelf = self;
    [_collectionView lsy_addDataSourceWithConfigBlock:^(LSYListViewDataSource * _Nonnull dataSource) {
        dataSource.startIndex = 1;
        dataSource.pageSize = 5;
        dataSource.removeFooterWhenNoMoreData = YES;
        dataSource.headerClass = GifRefreshHeader.class;
        dataSource.footerClass = GifRefreshFooter.class;
    } loadData:^(LSYListViewDataSource * _Nonnull dataSource, NSInteger pageIndex) {
        //请求数据
        [weakSelf loadDataWithPageIndex:pageIndex];
    } getCell:^UICollectionViewCell * _Nonnull(LSYCollectionViewDataSource * _Nonnull dataSource, id  _Nonnull data, NSIndexPath * _Nonnull indexPath) {
        CollectionViewCell *cell = [dataSource.collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
        cell.textLabel.text = data;
        return cell;
    }];
    [_collectionView.mj_header beginRefreshing];
    

    如代码所示,添加上边一系列的逻辑,只需要一个方法就能完成,该方法传入三个block作为入参。

    第一个block用来配置一些参数,第二个block用来根据pageIndex加载数据,第三个block用来实现cellForRowAtIndexPath功能;

    下面重点讲解一下可配置的参数:
    options:可以设置给listView添加header还是footer,默认都添加。
    startIndex:请求数据的起始索引值,默认0
    pageSize:不解释,默认10。
    removeFooterWhenNoMoreData:没有更多数据的时候是否移除footer,默认不移除。
    headerClass,footerClass:设置自定义的headerfooter类。

    然后,在数据请求完毕后,我们需要将数据回调给这个DataSource

    - (void)loadDataWithPageIndex:(NSInteger)pageIndex{
        XXXListRequest *request = [[XXXListRequest alloc] init];
        request.pageIndex = pageIndex;
        request.pageSize = _collectionView.lsy_dataSource.pageSize;
        [request startRequestWithSuccessBlock:^(XXXListResult *responseData) {
            _collectionView.lsy_dataSource.total = responseData.total;
            [_collectionView.lsy_dataSource endRefreshWithDataList:responseData.list];
        } failureBlock:^(NSError *error) {
            [_collectionView.lsy_dataSource endRefresh];
            //show fail toast
        }];
    }
    

    这样,一个列表页面的加载刷新逻辑就完成了,之前讲到的所有逻辑都不需要我们手动实现了,是不是简单了很多~

    现在,我们设置pageSize4total8,这是默认的headerfooter的效果:

    这是自定义的headerfooter

    Demo在这里,有兴趣的小伙伴可以下载下来看看~

    相关文章

      网友评论

          本文标题:基于MJRefresh的列表加载刷新逻辑的封装

          本文链接:https://www.haomeiwen.com/subject/ovhwudtx.html