大概是项目里太多的分页加载数据,所以一个简单、快捷、高效分页加载会使你那么的愉悦.
大概就是这么丝滑
github链接:JSLoadMoreService
用法讲解
属性预览
NSObject+LoadMoreService.h
/**
* 分页请求数量
*/
static NSInteger const PerPageMaxCount = 20;
@interface NSObject (LoadMoreService)
/**
* 每次请求追加的indexpaths
*/
@property (nonatomic, strong) NSMutableArray *appendingIndexpaths;
/**
* 数据数组
*/
@property (nonatomic, strong) NSMutableArray *dataArray;
/**
* 原始请求数据
*/
@property (nonatomic, strong) id orginResponseObject;
/**
* 当前页码
*/
@property (nonatomic, assign) NSInteger currentPage;
/**
* 是否请求中
*/
@property (nonatomic, assign) BOOL isRequesting;
/**
* 是否数据加载完
*/
@property (nonatomic, assign) BOOL isNoMoreData;
/**
* 单一请求分页加载数据
*
* @param baseURL 请求地址
* @param para 请求参数
* @param keyOfArray 取数组的key(注:多层请用/分隔)
* @param classNameOfModelArray 序列化model的class_name
* @param isReload (YES:刷新、NO:加载更多)
*
* @return RACSingal
*/
- (RACSignal *)js_singalForSingleRequestWithURL:(NSString *)baseURL
para:(NSMutableDictionary *)para
keyOfArray:(NSString *)keyOfArray
classNameOfModelArray:(NSString *)classNameOfModelArray
isReload:(BOOL)isReload;
@end
UITableView+Preload.h
/**
* 预加载触发的数量
*/
static NSInteger const PreloadMinCount = 10;
typedef void(^PreloadBlock)(void);
typedef void(^ReloadBlock)(void);
@interface UITableView (Prereload)
/**
* 预加载回调
*/
@property (nonatomic, copy ) PreloadBlock js_preloadBlock;
/**
* tableview数据
*/
@property (nonatomic, strong) NSMutableArray *dataArray;
/**
* 计算当前index是否达到预加载条件并回调
*
* @param currentIndex row or section
*/
- (void)preloadDataWithCurrentIndex:(NSInteger)currentIndex;
/**
* 上拉刷新
*
* @param js_reloadBlock 刷新回调
*/
- (void)headerReloadBlock:(ReloadBlock)js_reloadBlock;
/**
* 结束上拉刷新
*/
- (void)endReload;
如何调用
建一个viewModel类
这里处理数据的逻辑,所以写了方法 - (RACSignal *)siganlForJokeDataIsReload:(BOOL)isReload
下面就是怎样调用分类的方法:
RACReplaySubject *subject = [RACReplaySubject subject];
[[self js_singalForSingleRequestWithURL:Test_Page_URL
para:nil
keyOfArray:@"pdlist"
classNameOfModelArray:@"JSGoodListModel"
isReload:isReload] subscribeNext:^(id _Nullable x) {
/**
* x : 分类方法(js_singalForSingleRequestWithURL:...)里 sendNext 传过来的数组
* 你可以对每次传过来的数组的元素"再加工",知道达到你的要求后 再 sendNext
*/
//...
[subject sendNext:x];
} error:^(NSError * _Nullable error) {
[subject sendError:error];
} completed:^{
/**
* 走到这里为,每次分页请求所有逻辑处理完毕
*/
[subject sendCompleted];
}];
return subject;
}
VC调用:
整个方法:
- (void)requestGoodListIsReload:(BOOL)isReload{
kWeakSelf(self)
[[self.viewModel siganlForJokeDataIsReload:isReload] subscribeError:^(NSError * _Nullable error) {
} completed:^{
kStrongSelf(self)
self.listTableView.dataArray = self.viewModel.dataArray;
[self.listTableView reloadData];
[self.listTableView endReload];
}];
}
tableview里调用预加载
绘制cell代理里调用,根据你的需求是row or section
forRowAtIndexPath:(NSIndexPath *)indexPath
{
JSGoodListModel *model = self.dataArray[indexPath.row];
cell.textLabel.text = model.title;
cell.detailTextLabel.text = [NSString stringWithFormat:@"¥%@",model.price];
/**
* 根据当期index计算是否回调preloadblock
*/
[self preloadDataWithCurrentIndex:indexPath.row];
}
配置tableview的上拉刷新和预加载:
- (JSListTableView *)listTableView{
if (!_listTableView) {
_listTableView = [[JSListTableView alloc] initWithFrame:self.view.bounds
style:UITableViewStyleGrouped];
[self.view addSubview:_listTableView];
kWeakSelf(self)
/**
* 刷新
*/
[_listTableView headerReloadBlock:^{
kStrongSelf(self)
[self requestGoodListIsReload:YES];
}];
/**
* 预加载
*/
_listTableView.js_preloadBlock = ^{
kStrongSelf(self)
[self requestGoodListIsReload:NO];
};
}
return _listTableView;
}
至此,流程就done了
内部方法实现步骤
NSObject+LoadMoreService.m
先用runtime associate property
return [objc_getAssociatedObject(self, &key_isNoMoreData) boolValue];
}
- (void)setIsNoMoreData:(BOOL)isNoMoreData{
objc_setAssociatedObject(self, &key_isNoMoreData, @(isNoMoreData), OBJC_ASSOCIATION_ASSIGN);
}
- (BOOL)isRequesting{
return [objc_getAssociatedObject(self, &key_isRequesting) boolValue];
}
- (void)setIsRequesting:(BOOL)isRequesting{
objc_setAssociatedObject(self, &key_isRequesting, @(isRequesting), OBJC_ASSOCIATION_ASSIGN);
}
- (NSInteger)currentPage{
return [objc_getAssociatedObject(self, &key_currentPage) integerValue];
}
- (void)setCurrentPage:(NSInteger)currentPage{
objc_setAssociatedObject(self, &key_currentPage, @(currentPage), OBJC_ASSOCIATION_ASSIGN);
}
- (NSMutableArray *)dataArray{
return objc_getAssociatedObject(self, &key_dataArray);
}
- (void)setDataArray:(NSMutableArray *)dataArray{
objc_setAssociatedObject(self, &key_dataArray, dataArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSMutableArray *)appendingIndexpaths{
return objc_getAssociatedObject(self, &key_appendingIndexpaths);
}
- (void)setAppendingIndexpaths:(NSMutableArray *)appendingIndexpaths{
objc_setAssociatedObject(self, &key_appendingIndexpaths, appendingIndexpaths, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)orginResponseObject{
return objc_getAssociatedObject(self, &key_orginResponseObject);
}
- (void)setOrginResponseObject:(id)orginResponseObject{
objc_setAssociatedObject(self, &key_orginResponseObject, orginResponseObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
分页请求的base Method,
需要你配置的地方都有warning标识着:
para:(NSMutableDictionary *)para
isReload:(BOOL)isReload{
RACReplaySubject *subject = [RACReplaySubject subject];
if (![self isSatisfyLoadMoreRequest]&&!isReload) {
return subject;
}
if (!para) {
para = [NSMutableDictionary dictionary];
}
if (isReload) {
self.currentPage = 0;
#warning 此处可以添加统一的HUD
//...
}
self.currentPage++;
#warning 分页的key按需修改
para[@"page"] = @(self.currentPage);
para[@"per_page"] = @(PerPageMaxCount);
self.isRequesting = YES;
[[JSRequestTools js_getURL:baseURL para:para] subscribeNext:^(id _Nullable x) {
self.isRequesting = NO;
if (isReload) {
#warning 消失HUD
//...
}
[subject sendNext:x];
[subject sendCompleted];
} error:^(NSError * _Nullable error) {
self.isRequesting = NO;
if (self.currentPage>0) {
self.currentPage--;
}
[subject sendError:error];
}];
return subject;
}
此方法统一处理一些操作,比如:刷新remove,转model数组,记录是否加载完,记录当前请求的indexpath数组(为了是能调用insertRowsAtIndexPath:或者是insertSections:,而不用reloadData)
para:(NSMutableDictionary *)para
keyOfArray:(NSString *)keyOfArray
classNameOfModelArray:(NSString *)classNameOfModelArray
isReload:(BOOL)isReload{
RACReplaySubject *subject = [RACReplaySubject subject];
[[self js_baseSingleRequestWithURL:baseURL
para:para
isReload:isReload] subscribeNext:^(id _Nullable x) {
NSAssert(classNameOfModelArray, @"请建个对应的model,为了能创建数组模型!");
self.orginResponseObject = x;
if (!self.dataArray) {
self.dataArray = @[].mutableCopy;
}
if (isReload) {
[self.dataArray removeAllObjects];
}
NSArray *separateKeyArray = [keyOfArray componentsSeparatedByString:@"/"];
for (NSString *sepret_key in separateKeyArray) {
x = x[sepret_key];
}
NSArray *dataArray = [NSArray yy_modelArrayWithClass:NSClassFromString(classNameOfModelArray) json:x];
NSInteger from_index = self.dataArray.count;
NSInteger data_count = dataArray.count;
self.appendingIndexpaths = [self getAppendingIndexpathsFromIndex:from_index
appendingCount:data_count
inSection:0
isForRow:YES];
[subject sendNext:dataArray];
if (dataArray.count==0) {
self.isNoMoreData = YES;
} else {
self.isNoMoreData = NO;
[self.dataArray addObjectsFromArray:dataArray];
}
[subject sendCompleted];
} error:^(NSError * _Nullable error) {
[subject sendError:error];
}];
return subject;
}
判断是否满足预加载的条件:
return (!self.isNoMoreData&&!self.isRequesting);
}
获取当前分页的所得indexpaths数组:
appendingCount:(NSInteger)appendingCount
inSection:(NSInteger)inSection
isForRow:(BOOL)isForRow{
NSMutableArray *indexps = [NSMutableArray array];
for (NSInteger i = 0; i < appendingCount; i++) {
if (isForRow) {
NSIndexPath *indexp = [NSIndexPath indexPathForRow:from_index+i inSection:inSection];
[indexps addObject:indexp];
} else {
NSIndexPath *indexp = [NSIndexPath indexPathForRow:0 inSection:from_index+i];
[indexps addObject:indexp];
}
}
return indexps;
}
UITableView+Preload.m
给tableview扩展些属性以及方法
统一给tableview设置头部刷新
MJRefreshNormalHeader *header = [MJRefreshNormalHeader headerWithRefreshingBlock:js_reloadBlock];
self.mj_header = header;
}
结束刷新
[self.mj_header endRefreshing];
}
判断当前index是否可以出发预加载
NSInteger totalCount = self.dataArray.count;
if ([self isSatisfyPreloadDataWithTotalCount:totalCount currentIndex:currentIndex]&&self.js_preloadBlock) {
self.js_preloadBlock();
}
}
是否达到预加载的条件
return ((currentIndex == totalCount - PreloadMinCount) && (currentIndex >= PreloadMinCount));
}
注
依赖的三方库有:AFNetworking、ReactiveObjC、YYModel、MJRefresh
结
其实思路很简单,runtime扩展所需要的属性和方法,然后有机的结合调用,如果你真的看懂了,其实真的很方便,当然如果你有更好的建议都可以github issue我,共同学习共同进步~