利用预加载让分页加载不再繁琐之单个分页讲解

大概是项目里太多的分页加载数据,所以一个简单、快捷、高效分页加载会使你那么的愉悦.

大概就是这么丝滑

images
images

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我,共同学习共同进步~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,019评论 4 62
  • 最近在做项目时发现列表分页加载数据体验并不是很好,第一个想到解决此问题的方案就是预加载,便在网上找了一些相关的资料...
    ControlM阅读 2,482评论 0 4
  • 概述在iOS开发中UITableView可以说是使用最广泛的控件,我们平时使用的软件中到处都可以看到它的影子,类似...
    liudhkk阅读 8,985评论 3 38
  • 对于加载数据,感觉最容易上手的就是MJRefresh这个加载第三方库了。 记得将MJRefresh导入工程里面,不...
    大冲哥阅读 1,185评论 0 1
  • 我低着头 喝着碗里的汤 说我们的过去 再抬头时 只看到碗和散落的筷子 我追出门 你已在小道上 你用右手擦拭双脸 却...
    北疆牧者阅读 500评论 1 3