iOS 开发中下拉刷新和上拉加载逻辑的思考

对于普通的App来讲一般都会存在列表界面,而对于列表界面来说下拉刷新和上拉加载又是必不可少的,其实这些我们都会用的到,所以想把代码中用到的地方梳理下。以普通的首页列表页面为例子,刷新就以iOS中使用最为广泛的MJRefresh为例子讲解。

一、定义网络请求中不同的刷新状态

typedef NS_ENUM(NSInteger, LoadingType) {
    LoadingTypeNormal,//正常加载
    LoadingTypeRefresh,//下拉刷新
    LoadingTypeLoadMore//上拉加载
};

一般网络请求 我是放在 viewModel中去处理了,每个模块对应一个viewModel,这个viewModel 继承于BaseViewModel,所以上边的枚举 也在BaseViewMdoel中定义,方便子类的使用。 LoadingTypeNormal,//正常加载 这个状态一般适用于 首次进入列表页面 没有下拉刷新的情况下直接请求数据

二、定义各种刷新完成后的状态和网络请求方法
在.h文件中一般需要定义这些属性

@property (nonatomic, assign) LoadingType loadingType;// 刷新状态
@property (nonatomic, strong) NSError *error; //error信息
@property (nonatomic, assign) BOOL isListDataCompleted;//首次加载完成
@property (nonatomic, assign) BOOL isPullRefreshComplted;//下拉刷新加载完成
@property (nonatomic, assign) BOOL isLoadMoreComplted;// 上拉加载
@property (nonatomic, assign) BOOL isResetNoMoreData;// 上拉加载没有更多数据
@property (nonatomic, strong) NSMutableArray *dataArray;//返回的 数据的数组

通过BOOL值标记加载是否完成的状态,一般会分为首次加载完成、下拉刷新加载完成、上拉加载完成、上拉加载无更多数据然后通过RAC 或者 block将值进行回传给viewController ,viewController 通过返回的状态进行 数据的刷新或者其它的操作
一般请求的方法传入这两个参数就够了

- (void)requestWithParams:(NSDictionary *)params loadingType:(LoadingType)loadingType;

一个是 包含请求数据的Dictionary ,一个是请求的状态,因为只要这两个就够了,可能你会问 加载的page 和pageCount 不是还需要viewController处理吗,是的 下拉刷新和上拉加载肯定免不了对page和pageCount 处理,但是不需要在viewController 中 而是在viewModel中

@interface HomeViewModel()

@property (nonatomic, assign) NSInteger pageCount;
@property (nonatomic, assign) NSInteger page;

@end
- (instancetype)init {
    self = [super init];
    if (self) {
        self.pageCount = 20;
        self.page  = 1;
    }
    return self;
}

在 viewModel的.m文件中定义和初始化 page和pageCount,因为刷新和加载的数据处理都在viewModel中,viewController只关心数据源dataArray和刷新加载完成的状态所以在这里处理page和pageCount

三、处理请求逻辑

- (void)requestWithParams:(NSDictionary *)params loadingType:(LoadingType)loadingType {
    self.loadingType = loadingType;
    NSMutableDictionary *paramsDic = [NSMutableDictionary dictionaryWithDictionary:self.baseParams];
    [paramsDic addEntriesFromDictionary:self.userParams];
    [paramsDic addEntriesFromDictionary:self.baseParams];
    if (self.loadingType == LoadingTypeNormal || self.loadingType == LoadingTypeRefresh) {
        self.page = 1;
        [paramsDic setObject:@(self.page) forKey:@"page"];
    } else {
        [paramsDic setObject:@(self.page + 1) forKey:@"page"];
    }
    [paramsDic setObject:@(self.pageCount) forKey:@"page_count"];
    BOOL isNeedLoading = self.loadingType == LoadingTypeNormal ? YES : NO;
    [[LLNetwork shareInstance] getWithURL:@"APIManager.home"
                                    token:nil
                                   params:paramsDic
                                isLoading:isNeedLoading
                                  success:^(id response) {
        self.response = response;
        [self handleDataWithResponse:response];
    } failure:^(NSError *error) {
        self.error = error;
    }];
}

这里关键的点在于 page 和pageCount参数的处理,在正常请求加载的情况下,需要将
self.page = 1;
因为此时请求的一般是首页数据 page就传1
当为上拉加载的时候 为什么

      [paramsDic setObject:@(self.page + 1) forKey:@"page"];

而不是

      [paramsDic setObject:@(self.page ++) forKey:@"page"];

咋一看 这样处理毫无差别 ,都是传递的page 加 1,其实下边的处理是不妥的,因为上拉加载只能是 加载成功了 page 才能进行++的,试想这样的case 网络不好 我正在加载第2页,上拉加载一直加载失败 我如果self.page++ 那么 当网络恢复良好的时候 此时self.page不知道是多少了,也不是我想要的第二页了,所以此时不改变 self.page 的值 只是将传递的参数 为
self.page + 1 上拉加载成功后在进行 self.page++ ,这样做保证了self.page 值得正确性

 self.loadingType = loadingType;

注意每次请求都要更新下现在的请求状态,因为后续的数据处理需要依赖这个状态

四、处理接受数据后的逻辑
对于数据成功后的处理

- (void)handleDataWithResponse:(id)response {
    DebugLog(@"首页数据=%@",response);
    BOOL isSuccess = [response[@"is_success"] boolValue];
    if (!isSuccess) {
        NSString *errorMessage = response[@"error_msg"];
        DebugLog(errorMessage)
        return;
    }

    NSDictionary *dataDic = response[@"data"];
    NSArray *producrsArray = dataDic[@"product_list"];
    NSArray *listArray = [HomeModel mj_objectArrayWithKeyValuesArray:producrsArray];
    NSMutableArray *tempDataArray = [NSMutableArray arrayWithArray:listArray];
    
    if (tempDataArray.count < self.pageCount) {
        self.isResetNoMoreData = YES;
    } else {
        self.isResetNoMoreData = NO;
    }
    
    if (self.loadingType == LoadingTypeNormal) {//首次加载
        self.dataArray = tempDataArray;
        self.isListDataCompleted = YES;
        
    } else if (self.loadingType == LoadingTypeRefresh) {//下拉刷新
        self.dataArray = tempDataArray;
        self.isPullRefreshComplted = YES;
        
    } else if (self.loadingType == LoadingTypeLoadMore) {//上拉加载
        [self.dataArray addObjectsFromArray:tempDataArray];
        self.isLoadMoreComplted = YES;
        self.page++;
    }
}

每次请求结束 对于是否有更多数据的判断是 当次请求的数据条数是否小于pageCount,如果小于则判断为最后一页 ,再次上拉加载需要提示 " 没有更多数据"

 if (tempDataArray.count < self.pageCount) {
        self.isResetNoMoreData = YES;
    } else {
        self.isResetNoMoreData = NO;
    }

四、viewModel和viewController交互逻辑处理
前边提到的

@property (nonatomic, assign) BOOL isListDataCompleted;//首次加载完成
@property (nonatomic, assign) BOOL isPullRefreshComplted;//下拉刷新加载完成
@property (nonatomic, assign) BOOL isLoadMoreComplted;// 上拉加载
@property (nonatomic, assign) BOOL isResetNoMoreData;// 上拉加载没有更多数据

这些状态 viewController是需要知道的 ,因为 loading框的显示隐藏、刷新菊花的显示与停止都需要这些状态的判断才能做出下一步的操作,所以我们还是需要将这些状态进行回传,当然回传值 的方法有很多,delegate、block等,笔者偏向于 使用RAC 这样代码比较简略
在viewController的代码入下

#import "HomeViewController.h"
#import "HomeViewModel.h"

@interface HomeViewController ()

@property (nonatomic, strong) HomeViewModel *mainViewModel;

@end

@implementation HomeViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self add_Observer];
}

#pragma mark Priviate - methods
- (void)add_Observer {
    @weakify(self);
    [[RACObserve(self.mainViewModel, error)ignore:nil]subscribeNext:^(NSError *error) {
        @strongify(self);
        DebugLog(@"%ld, %@", error.code , error.description);
        [self handleErrorInfo:error];
        
    }];
    
    [[RACObserve(self.mainViewModel, fixedFilterModel)ignore:nil]subscribeNext:^(FixedFilterModel *model) {
        @strongify(self);
        self.fixedFilterModel = model;
    }];
    
    
    [[RACObserve(self.mainViewModel, isListDataCompleted)ignore:nil]subscribeNext:^(NSNumber *value) {
        @strongify(self);
        if ([value integerValue]) {
            [self hideSeverErrorView];
            [self hideNetworkUnReachableView];
            [self.listView  reloadData];
            if (self.mainViewModel.isResetNoMoreData) {
                [self.listView.listTableView footerEndRefreshingWithNoMoreData];//footer 停止加载显示 无更多数据
            } else {
                [self.listView.listTableView footerEndRefreshing];//footer 仅仅停止加载
            }
        }
    }];
    
    //下拉完成
    [[RACObserve(self.mainViewModel, isPullRefreshComplted)ignore:nil]subscribeNext:^(NSNumber *value) {
        @strongify(self);
        if ([value integerValue]) {
            [self.listView.listTableView headerEndRefreshing];
            [self.listView  reloadData];
            if (self.mainViewModel.isResetNoMoreData) { //footer 停止加载显示 无更多数据
                [self.listView.listTableView footerEndRefreshingWithNoMoreData];
            } else {
                [self.listView.listTableView footerEndRefreshing];//footer 仅仅停止加载
            }
        }
    }];
    
    
    // 上拉加载
    [[RACObserve(self.mainViewModel, isLoadMoreComplted)ignore:nil]subscribeNext:^(NSNumber *value) {
        @strongify(self);
        if ([value integerValue]) {
            [self.listView  reloadData];
            if (self.mainViewModel.isResetNoMoreData) {
                [self.listView.listTableView footerEndRefreshingWithNoMoreData];//footer 停止加载显示 无更多数据
            } else {
                [self.listView.listTableView footerEndRefreshing]; // footer 仅仅停止加载
            }
        }
    }];
    
}



- (HomeViewModel *)mainViewModel {
    if (!_mainViewModel) {
        _mainViewModel = [[HomeViewModel alloc] init];
    }
    return _mainViewModel;
}

@end

从代码逻辑也能看到,viewController作用主要为获取各种状态然后处理这些状态和进行数据的刷新加载
需要注意的点是在 正常加载和下拉刷新的时候也需要判断 数据是否加载完成了 ,因为假如 pageCount == 10 的情况下 如果只有5条数据那么 首次加载后 根本不需要下拉刷新和上拉加载就已经加载完了数据,此时就要更新footer显示无更多数据了。
上边就是简单的对于下拉刷新和上拉加载 自己的一点见解,在项目中也是这样用的,感觉不错的地方做个记录,欢迎各位批评指正。
代码地址 :https://git.coding.net/liwb/UserProject.git

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