大概是项目里太多的分页加载数据,所以一个简单、快捷、高效分页加载会使你那么的愉悦.
大概就是这么丝滑
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 ``` 下面就是怎样调用分类的方法: ```- (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{ 1 kWeakSelf(self) [[self.viewModel siganlForJokeDataIsReload:isReload] subscribeError:^(NSError * _Nullable error) { 1 } completed:^{ kStrongSelf(self) self.listTableView.dataArray = self.viewModel.dataArray; [self.listTableView reloadData]; [self.listTableView endReload]; }]; }
tableview里调用预加载
绘制cell代理里调用,根据你的需求是row or section
(void)configureCell:(UITableViewCell *)cell 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
(BOOL)isNoMoreData{ 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标识着:
(RACSignal *)js_baseSingleRequestWithURL:(NSString *)baseURL 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]; }]; 1 return subject; }
此方法统一处理一些操作,比如:刷新remove,转model数组,记录是否加载完,记录当前请求的indexpath数组(为了是能调用insertRowsAtIndexPath:或者是insertSections:,而不用reloadData)
(RACSignal *)js_singalForSingleRequestWithURL:(NSString *)baseURL 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; }
判断是否满足预加载的条件:
(BOOL)isSatisfyLoadMoreRequest{ return (!self.isNoMoreData&&!self.isRequesting); }
获取当前分页的所得indexpaths数组:
(NSMutableArray *)getAppendingIndexpathsFromIndex:(NSInteger)from_index 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设置头部刷新
(void)headerReloadBlock:(ReloadBlock)js_reloadBlock{ MJRefreshNormalHeader *header = [MJRefreshNormalHeader headerWithRefreshingBlock:js_reloadBlock]; self.mj_header = header; }
结束刷新
(void)endReload{ [self.mj_header endRefreshing]; }
判断当前index是否可以出发预加载
(void)preloadDataWithCurrentIndex:(NSInteger)currentIndex{ NSInteger totalCount = self.dataArray.count; if ([self isSatisfyPreloadDataWithTotalCount:totalCount currentIndex:currentIndex]&&self.js_preloadBlock) { self.js_preloadBlock(); } }
是否达到预加载的条件
(BOOL)isSatisfyPreloadDataWithTotalCount:(NSInteger)totalCount currentIndex:(NSInteger)currentIndex{ return ((currentIndex == totalCount - PreloadMinCount) && (currentIndex >= PreloadMinCount)); }
注
依赖的三方库有:AFNetworking、ReactiveObjC、YYModel、MJRefresh
结
其实思路很简单,runtime扩展所需要的属性和方法,然后有机的结合调用,如果你真的看懂了,其实真的很方便,当然如果你有更好的建议都可以github issue我,共同学习共同进步~