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逻辑
讲到加载和刷新,就离不开PageSize
和PageIndex
,后端给我们的接口一定会包含这两个参数,为了处理相关逻辑,我们需要在控制器里增加对应的两个属性,然后在合适的地方给pageSize
赋值,比如viewDidLoad
方法中。
这里有一点需要注意的是,PageIndex
的初始值是后端定的,所以不一定是0
,也有可能是其他值。
Total逻辑
还有一个容易忽略的就是Total
逻辑,类似的接口返回值中一般都会带个Total
字段,表示后台数据总量。
我们可以用这个值来判断,是否还有数据可以加载,当然,并不是所有的后端都有返回这么一个字段的习惯,如果没有这个字段,我们还可以根据返回的元素数量是否等于PageSize
来判断,只是这种判断方法稍差于用Total
判断,当某一次请求返回的元素数量等于PageSize
,而此时后台恰好也没有更多数据的时候,Total
判断法直接就可以进行判断,而计算size法
需要在下次加载结束后才能判断。
当没有更多数据的时候,我们需要对用户进行提示,常用的提示方法一般有两种:
- 将
refreshFooter
的状态置为NoMoreData
,并根据需要设置显示的文案。 - 直接将
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
:设置自定义的header
和footer
类。
然后,在数据请求完毕后,我们需要将数据回调给这个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
}];
}
这样,一个列表页面的加载刷新逻辑就完成了,之前讲到的所有逻辑都不需要我们手动实现了,是不是简单了很多~
现在,我们设置pageSize
为4
,total
为8
,这是默认的header
和footer
的效果:
这是自定义的header
和footer
:
Demo在这里,有兴趣的小伙伴可以下载下来看看~