原文
大概是項目裡太多的分頁加載數據,所以一個簡單、快捷、高效分頁加載會使你那麼的愉悅.
大概就是這麼絲滑
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我,共同學習共同進步~