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

你可能感兴趣的:(iOS 开发中下拉刷新和上拉加载逻辑的思考)