大概是項目里太多的分頁加載數(shù)據(jù),所以一個簡單、快捷、高效分頁加載會使你那么的愉悅.
大概就是這么絲滑

github鏈接:JSLoadMoreService
用法講解
屬性預(yù)覽
NSObject+LoadMoreService.h
/**
* 分頁請求數(shù)量
*/
static NSInteger const PerPageMaxCount = 20;
@interface NSObject (LoadMoreService)
/**
* 每次請求追加的indexpaths
*/
@property (nonatomic, strong) NSMutableArray *appendingIndexpaths;
/**
* 數(shù)據(jù)數(shù)組
*/
@property (nonatomic, strong) NSMutableArray *dataArray;
/**
* 原始請求數(shù)據(jù)
*/
@property (nonatomic, strong) id orginResponseObject;
/**
* 當(dāng)前頁碼
*/
@property (nonatomic, assign) NSInteger currentPage;
/**
* 是否請求中
*/
@property (nonatomic, assign) BOOL isRequesting;
/**
* 是否數(shù)據(jù)加載完
*/
@property (nonatomic, assign) BOOL isNoMoreData;
/**
* 單一請求分頁加載數(shù)據(jù)
*
* @param baseURL 請求地址
* @param para 請求參數(shù)
* @param keyOfArray 取數(shù)組的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
/**
* 預(yù)加載觸發(fā)的數(shù)量
*/
static NSInteger const PreloadMinCount = 10;
typedef void(^PreloadBlock)(void);
typedef void(^ReloadBlock)(void);
@interface UITableView (Prereload)
/**
* 預(yù)加載回調(diào)
*/
@property (nonatomic, copy ) PreloadBlock js_preloadBlock;
/**
* tableview數(shù)據(jù)
*/
@property (nonatomic, strong) NSMutableArray *dataArray;
/**
* 計算當(dāng)前index是否達(dá)到預(yù)加載條件并回調(diào)
*
* @param currentIndex row or section
*/
- (void)preloadDataWithCurrentIndex:(NSInteger)currentIndex;
/**
* 上拉刷新
*
* @param js_reloadBlock 刷新回調(diào)
*/
- (void)headerReloadBlock:(ReloadBlock)js_reloadBlock;
/**
* 結(jié)束上拉刷新
*/
- (void)endReload;
如何調(diào)用
建一個viewModel類
這里處理數(shù)據(jù)的邏輯,所以寫了方法 - (RACSignal *)siganlForJokeDataIsReload:(BOOL)isReload
下面就是怎樣調(diào)用分類的方法:
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 傳過來的數(shù)組
* 你可以對每次傳過來的數(shù)組的元素"再加工",知道達(dá)到你的要求后 再 sendNext
*/
//...
[subject sendNext:x];
} error:^(NSError * _Nullable error) {
[subject sendError:error];
} completed:^{
/**
* 走到這里為,每次分頁請求所有邏輯處理完畢
*/
[subject sendCompleted];
}];
return subject;
}
VC調(diào)用:
整個方法:
- (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里調(diào)用預(yù)加載
繪制cell代理里調(diào)用,根據(jù)你的需求是row or section
forRowAtIndexPath:(NSIndexPath *)indexPath
{
JSGoodListModel *model = self.dataArray[indexPath.row];
cell.textLabel.text = model.title;
cell.detailTextLabel.text = [NSString stringWithFormat:@"¥%@",model.price];
/**
* 根據(jù)當(dāng)期index計算是否回調(diào)preloadblock
*/
[self preloadDataWithCurrentIndex:indexPath.row];
}
配置tableview的上拉刷新和預(yù)加載:
- (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];
}];
/**
* 預(yù)加載
*/
_listTableView.js_preloadBlock = ^{
kStrongSelf(self)
[self requestGoodListIsReload:NO];
};
}
return _listTableView;
}
至此,流程就done了
內(nèi)部方法實現(xiàn)步驟
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標(biāo)識著:
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 此處可以添加統(tǒng)一的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;
}
此方法統(tǒng)一處理一些操作,比如:刷新remove,轉(zhuǎn)model數(shù)組,記錄是否加載完,記錄當(dāng)前請求的indexpath數(shù)組(為了是能調(diào)用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, @"請建個對應(yīng)的model,為了能創(chuàng)建數(shù)組模型!");
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;
}
判斷是否滿足預(yù)加載的條件:
return (!self.isNoMoreData&&!self.isRequesting);
}
獲取當(dāng)前分頁的所得indexpaths數(shù)組:
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擴(kuò)展些屬性以及方法
統(tǒng)一給tableview設(shè)置頭部刷新
MJRefreshNormalHeader *header = [MJRefreshNormalHeader headerWithRefreshingBlock:js_reloadBlock];
self.mj_header = header;
}
結(jié)束刷新
[self.mj_header endRefreshing];
}
判斷當(dāng)前index是否可以出發(fā)預(yù)加載
NSInteger totalCount = self.dataArray.count;
if ([self isSatisfyPreloadDataWithTotalCount:totalCount currentIndex:currentIndex]&&self.js_preloadBlock) {
self.js_preloadBlock();
}
}
是否達(dá)到預(yù)加載的條件
return ((currentIndex == totalCount - PreloadMinCount) && (currentIndex >= PreloadMinCount));
}
注
依賴的三方庫有:AFNetworking、ReactiveObjC、YYModel、MJRefresh
結(jié)
其實思路很簡單,runtime擴(kuò)展所需要的屬性和方法,然后有機(jī)的結(jié)合調(diào)用,如果你真的看懂了,其實真的很方便,當(dāng)然如果你有更好的建議都可以github issue我,共同學(xué)習(xí)共同進(jìn)步~