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

2023-08-22  本文已影响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在这里,有兴趣的小伙伴可以下载下来看看~

上一篇 下一篇

猜你喜欢

热点阅读