iOS 開發(fā)中下拉刷新和上拉加載邏輯的思考

對于普通的App來講一般都會存在列表界面,而對于列表界面來說下拉刷新和上拉加載又是必不可少的,其實這些我們都會用的到,所以想把代碼中用到的地方梳理下。以普通的首頁列表頁面為例子,刷新就以iOS中使用最為廣泛的MJRefresh為例子講解。

一、定義網(wǎng)絡請求中不同的刷新狀態(tài)

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

一般網(wǎng)絡請求 我是放在 viewModel中去處理了,每個模塊對應一個viewModel,這個viewModel 繼承于BaseViewModel,所以上邊的枚舉 也在BaseViewMdoel中定義,方便子類的使用。 LoadingTypeNormal,//正常加載 這個狀態(tài)一般適用于 首次進入列表頁面 沒有下拉刷新的情況下直接請求數(shù)據(jù)

二、定義各種刷新完成后的狀態(tài)和網(wǎng)絡請求方法
在.h文件中一般需要定義這些屬性

@property (nonatomic, assign) LoadingType loadingType;// 刷新狀態(tài)
@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;// 上拉加載沒有更多數(shù)據(jù)
@property (nonatomic, strong) NSMutableArray *dataArray;//返回的 數(shù)據(jù)的數(shù)組

通過BOOL值標記加載是否完成的狀態(tài),一般會分為首次加載完成、下拉刷新加載完成、上拉加載完成、上拉加載無更多數(shù)據(jù)然后通過RAC 或者 block將值進行回傳給viewController ,viewController 通過返回的狀態(tài)進行 數(shù)據(jù)的刷新或者其它的操作
一般請求的方法傳入這兩個參數(shù)就夠了

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

一個是 包含請求數(shù)據(jù)的Dictionary ,一個是請求的狀態(tài),因為只要這兩個就夠了,可能你會問 加載的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,因為刷新和加載的數(shù)據(jù)處理都在viewModel中,viewController只關心數(shù)據(jù)源dataArray和刷新加載完成的狀態(tài)所以在這里處理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參數(shù)的處理,在正常請求加載的情況下,需要將
self.page = 1;
因為此時請求的一般是首頁數(shù)據(jù) page就傳1
當為上拉加載的時候 為什么

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

而不是

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

咋一看 這樣處理毫無差別 ,都是傳遞的page 加 1,其實下邊的處理是不妥的,因為上拉加載只能是 加載成功了 page 才能進行++的,試想這樣的case 網(wǎng)絡不好 我正在加載第2頁,上拉加載一直加載失敗 我如果self.page++ 那么 當網(wǎng)絡恢復良好的時候 此時self.page不知道是多少了,也不是我想要的第二頁了,所以此時不改變 self.page 的值 只是將傳遞的參數(shù) 為
self.page + 1 上拉加載成功后在進行 self.page++ ,這樣做保證了self.page 值得正確性

 self.loadingType = loadingType;

注意每次請求都要更新下現(xiàn)在的請求狀態(tài),因為后續(xù)的數(shù)據(jù)處理需要依賴這個狀態(tài)

四、處理接受數(shù)據(jù)后的邏輯
對于數(shù)據(jù)成功后的處理

- (void)handleDataWithResponse:(id)response {
    DebugLog(@"首頁數(shù)據(jù)=%@",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++;
    }
}

每次請求結束 對于是否有更多數(shù)據(jù)的判斷是 當次請求的數(shù)據(jù)條數(shù)是否小于pageCount,如果小于則判斷為最后一頁 ,再次上拉加載需要提示 " 沒有更多數(shù)據(jù)"

 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;// 上拉加載沒有更多數(shù)據(jù)

這些狀態(tài) viewController是需要知道的 ,因為 loading框的顯示隱藏、刷新菊花的顯示與停止都需要這些狀態(tài)的判斷才能做出下一步的操作,所以我們還是需要將這些狀態(tài)進行回傳,當然回傳值 的方法有很多,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 停止加載顯示 無更多數(shù)據(jù)
            } 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 停止加載顯示 無更多數(shù)據(jù)
                [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 停止加載顯示 無更多數(shù)據(jù)
            } else {
                [self.listView.listTableView footerEndRefreshing]; // footer 僅僅停止加載
            }
        }
    }];
    
}



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

@end

從代碼邏輯也能看到,viewController作用主要為獲取各種狀態(tài)然后處理這些狀態(tài)和進行數(shù)據(jù)的刷新加載
需要注意的點是在 正常加載和下拉刷新的時候也需要判斷 數(shù)據(jù)是否加載完成了 ,因為假如 pageCount == 10 的情況下 如果只有5條數(shù)據(jù)那么 首次加載后 根本不需要下拉刷新和上拉加載就已經(jīng)加載完了數(shù)據(jù),此時就要更新footer顯示無更多數(shù)據(jù)了。
上邊就是簡單的對于下拉刷新和上拉加載 自己的一點見解,在項目中也是這樣用的,感覺不錯的地方做個記錄,歡迎各位批評指正。
代碼地址 :https://git.coding.net/liwb/UserProject.git

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容