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

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

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

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

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

二腮猖、定義各種刷新完成后的狀態(tài)和網(wǎng)絡(luò)請(qǐ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值標(biāo)記加載是否完成的狀態(tài),一般會(huì)分為首次加載完成赞枕、下拉刷新加載完成澈缺、上拉加載完成、上拉加載無更多數(shù)據(jù)然后通過RAC 或者 block將值進(jìn)行回傳給viewController ,viewController 通過返回的狀態(tài)進(jìn)行 數(shù)據(jù)的刷新或者其它的操作
一般請(qǐng)求的方法傳入這兩個(gè)參數(shù)就夠了

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

一個(gè)是 包含請(qǐng)求數(shù)據(jù)的Dictionary 炕婶,一個(gè)是請(qǐng)求的狀態(tài)姐赡,因?yàn)橹灰@兩個(gè)就夠了,可能你會(huì)問 加載的page 和pageCount 不是還需要viewController處理嗎柠掂,是的 下拉刷新和上拉加載肯定免不了對(duì)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,因?yàn)樗⑿潞图虞d的數(shù)據(jù)處理都在viewModel中涯贞,viewController只關(guān)心數(shù)據(jù)源dataArray和刷新加載完成的狀態(tài)所以在這里處理page和pageCount

三枪狂、處理請(qǐng)求邏輯

- (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;
    }];
}

這里關(guān)鍵的點(diǎn)在于 page 和pageCount參數(shù)的處理,在正常請(qǐng)求加載的情況下宋渔,需要將
self.page = 1;
因?yàn)榇藭r(shí)請(qǐng)求的一般是首頁數(shù)據(jù) page就傳1
當(dāng)為上拉加載的時(shí)候 為什么

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

而不是

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

咋一看 這樣處理毫無差別 州疾,都是傳遞的page 加 1,其實(shí)下邊的處理是不妥的傻谁,因?yàn)樯侠虞d只能是 加載成功了 page 才能進(jìn)行++的孝治,試想這樣的case 網(wǎng)絡(luò)不好 我正在加載第2頁,上拉加載一直加載失敗 我如果self.page++ 那么 當(dāng)網(wǎng)絡(luò)恢復(fù)良好的時(shí)候 此時(shí)self.page不知道是多少了审磁,也不是我想要的第二頁了谈飒,所以此時(shí)不改變 self.page 的值 只是將傳遞的參數(shù) 為
self.page + 1 上拉加載成功后在進(jìn)行 self.page++ ,這樣做保證了self.page 值得正確性

 self.loadingType = loadingType;

注意每次請(qǐng)求都要更新下現(xiàn)在的請(qǐng)求狀態(tài),因?yàn)楹罄m(xù)的數(shù)據(jù)處理需要依賴這個(gè)狀態(tài)

四态蒂、處理接受數(shù)據(jù)后的邏輯
對(duì)于數(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++;
    }
}

每次請(qǐng)求結(jié)束 對(duì)于是否有更多數(shù)據(jù)的判斷是 當(dāng)次請(qǐng)求的數(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是需要知道的 手素,因?yàn)?loading框的顯示隱藏、刷新菊花的顯示與停止都需要這些狀態(tài)的判斷才能做出下一步的操作瘩蚪,所以我們還是需要將這些狀態(tài)進(jìn)行回傳泉懦,當(dāng)然回傳值 的方法有很多,delegate疹瘦、block等崩哩,筆者偏向于 使用RAC 這樣代碼比較簡(jiǎn)略
在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)和進(jìn)行數(shù)據(jù)的刷新加載
需要注意的點(diǎn)是在 正常加載和下拉刷新的時(shí)候也需要判斷 數(shù)據(jù)是否加載完成了 ,因?yàn)榧偃?pageCount == 10 的情況下 如果只有5條數(shù)據(jù)那么 首次加載后 根本不需要下拉刷新和上拉加載就已經(jīng)加載完了數(shù)據(jù)邓嘹,此時(shí)就要更新footer顯示無更多數(shù)據(jù)了酣栈。
上邊就是簡(jiǎn)單的對(duì)于下拉刷新和上拉加載 自己的一點(diǎn)見解,在項(xiàng)目中也是這樣用的汹押,感覺不錯(cuò)的地方做個(gè)記錄矿筝,歡迎各位批評(píng)指正。
代碼地址 :https://git.coding.net/liwb/UserProject.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末棚贾,一起剝皮案震驚了整個(gè)濱河市窖维,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌妙痹,老刑警劉巖陈辱,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異细诸,居然都是意外死亡沛贪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門震贵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來利赋,“玉大人,你說我怎么就攤上這事猩系∶乃停” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵寇甸,是天一觀的道長(zhǎng)塘偎。 經(jīng)常有香客問我,道長(zhǎng)拿霉,這世上最難降的妖魔是什么吟秩? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮绽淘,結(jié)果婚禮上涵防,老公的妹妹穿的比我還像新娘。我一直安慰自己沪铭,他們只是感情好壮池,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著杀怠,像睡著了一般椰憋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赔退,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天橙依,我揣著相機(jī)與錄音,去河邊找鬼。 笑死票编,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的卵渴。 我是一名探鬼主播慧域,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼浪读!你這毒婦竟也來了昔榴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤碘橘,失蹤者是張志新(化名)和其女友劉穎互订,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痘拆,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仰禽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纺蛆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吐葵。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖桥氏,靈堂內(nèi)的尸體忽然破棺而出温峭,到底是詐尸還是另有隱情,我是刑警寧澤字支,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布凤藏,位于F島的核電站,受9級(jí)特大地震影響堕伪,放射性物質(zhì)發(fā)生泄漏揖庄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一欠雌、第九天 我趴在偏房一處隱蔽的房頂上張望抠艾。 院中可真熱鬧,春花似錦桨昙、人聲如沸检号。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽齐苛。三九已至,卻和暖如春桂塞,著一層夾襖步出監(jiān)牢的瞬間凹蜂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留玛痊,地道東北人汰瘫。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像擂煞,于是被迫代替她去往敵國(guó)和親混弥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348