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

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在这里,有兴趣的小伙伴可以下载下来看看~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容