這篇文章主要介紹“怎么寫好一個UITableView”,在日常操作中,相信很多人在怎么寫好一個UITableView問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”怎么寫好一個UITableView”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
成都創(chuàng)新互聯(lián)公司咨詢電話:18980820575,為您提供成都網(wǎng)站建設(shè)網(wǎng)頁設(shè)計及定制高端網(wǎng)站建設(shè)服務(wù),成都創(chuàng)新互聯(lián)公司網(wǎng)頁制作領(lǐng)域十載,包括辦公窗簾等多個行業(yè)擁有多年的網(wǎng)站設(shè)計經(jīng)驗,選擇成都創(chuàng)新互聯(lián)公司,為企業(yè)保駕護航。
如果你覺得 `UITableViewDelegate` 和 `UITableViewDataSource` 這兩個協(xié)議中有大量方法每次都是復(fù)制粘貼,實現(xiàn)起來大同小異;如果你覺得發(fā)起網(wǎng)絡(luò)請求并解析數(shù)據(jù)需要一大段代碼,加上刷新和加載后簡直復(fù)雜度爆表,如果你想知道為什么下面的代碼可以滿足上述所有要求:
在討論解耦之前,我們要弄明白 MVC 的核心:控制器(以下簡稱 C)負責(zé)模型(以下簡稱 M)和視圖(以下簡稱 V)的交互。
這里所說的 M,通常不是一個單獨的類,很多情況下它是由多個類構(gòu)成的一個層。最上層的通常是以 `Model` 結(jié)尾的類,它直接被 C 持有。`Model` 類還可以持有兩個對象:
1. Item:它是實際存儲數(shù)據(jù)的對象。它可以理解為一個字典,和 V 中的屬性一一對應(yīng)
2. Cache:它可以緩存自己的 Item(如果有很多)
常見的誤區(qū):
1. 一般情況下數(shù)據(jù)的處理會放在 M 而不是 C(C 只做不能復(fù)用的事)
2. 解耦不只是把一段代碼拿到外面去。而是關(guān)注是否能合并重復(fù)代碼, 并且有良好的拖展性。
在 C 中,我們創(chuàng)建 `UITableView` 對象,然后將它的數(shù)據(jù)源和代理設(shè)置為自己。也就是自己管理著 UI 邏輯和數(shù)據(jù)存取的邏輯。在這種架構(gòu)下,主要存在這些問題:
1. 違背 MVC 模式,現(xiàn)在是 V 持有 C 和 M。
2. C 管理了全部邏輯,耦合太嚴(yán)重。
3. 其實絕大多數(shù) UI 相關(guān)都是由 Cell 而不是 `UITableView` 自身完成的。
為了解決這些問題,我們首先弄明白,數(shù)據(jù)源和代理分別做了那些事。
它有兩個必須實現(xiàn)的代理方法:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section; - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
簡單來說,只要實現(xiàn)了這個兩個方法,一個簡單的 `UITableView` 對象就算是完成了。
除此以外,它還負責(zé)管理 `section` 的數(shù)量,標(biāo)題,某一個 `cell` 的編輯和移動等。
代理主要涉及以下幾個方面的內(nèi)容:
1. cell、headerView 等展示前、后的回調(diào)。
2. cell、headerView 等的高度,點擊事件。
最常用的也是兩個方法:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath; - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
提醒:絕大多數(shù)代理方法都有一個 `indexPath` 參數(shù)
最簡單的思路是單獨把數(shù)據(jù)源拿出來作為一個對象。
這種寫法有一定的解耦作用,同時可以有效減少 C 中的代碼量。然而總代碼量會上升。我們的目標(biāo)是減少不必要的代碼。
比如獲取每一個 `section` 的行數(shù),它的實現(xiàn)邏輯總是高度類似。然而由于數(shù)據(jù)源的具體實現(xiàn)方式不統(tǒng)一,所以每個數(shù)據(jù)源都要重新實現(xiàn)一遍。
首先我們來思考一個問題,數(shù)據(jù)源作為 M,它持有的 Item 長什么樣?答案是一個二維數(shù)組,每個元素保存了一個 `section` 所需要的全部信息。因此除了有自己的數(shù)組(給cell用)外,還有 section 的標(biāo)題等,我們把這樣的元素命名為 `SectionObject`:
@interface KtTableViewSectionObject : NSObject @property (nonatomic, copy) NSString *headerTitle; // UITableDataSource 協(xié)議中的 titleForHeaderInSection 方法可能會用到 @property (nonatomic, copy) NSString *footerTitle; // UITableDataSource 協(xié)議中的 titleForFooterInSection 方法可能會用到 @property (nonatomic, retain) NSMutableArray *items; - (instancetype)initWithItemArray:(NSMutableArray *)items; @end
其中的 `items` 數(shù)組,應(yīng)該存儲了每個 cell 所需要的 `Item`,考慮到 `Cell` 的特點,基類的 `BaseItem` 可以設(shè)計成這樣:
@interface KtTableViewBaseItem : NSObject @property (nonatomic, retain) NSString *itemIdentifier; @property (nonatomic, retain) UIImage *itemImage; @property (nonatomic, retain) NSString *itemTitle; @property (nonatomic, retain) NSString *itemSubtitle; @property (nonatomic, retain) UIImage *itemAccessoryImage; - (instancetype)initWithImage:(UIImage *)image Title:(NSString *)title SubTitle:(NSString *)subTitle AccessoryImage:(UIImage *)accessoryImage; @end
規(guī)定好了統(tǒng)一的數(shù)據(jù)存儲格式以后,我們就可以考慮在基類中完成某些方法了。以 `- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section` 方法為例,它可以這樣實現(xiàn):
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { if (self.sections.count > section) { KtTableViewSectionObject *sectionObject = [self.sections objectAtIndex:section]; return sectionObject.items.count; } return 0; }
比較困難的是創(chuàng)建 `cell`,因為我們不知道 `cell` 的類型,自然也就無法調(diào)用 `alloc` 方法。除此以外,`cell` 除了創(chuàng)建,還需要設(shè)置 UI,這些都是數(shù)據(jù)源不應(yīng)該做的事。
這兩個問題的解決方案如下:
1. 定義一個協(xié)議,父類返回基類 `Cell`,子類視情況返回合適的類型。
2. 為 `Cell` 添加一個 `setObject` 方法,用于解析 Item 并更新 UI。
經(jīng)過這一番折騰,好處是相當(dāng)明顯的:
1. 子類的數(shù)據(jù)源只需要實現(xiàn) `cellClassForObject` 方法即可。原來的數(shù)據(jù)源方法已經(jīng)在父類中被統(tǒng)一實現(xiàn)了。
2. 每一個 Cell 只要寫好自己的 `setObject` 方法,然后坐等自己被創(chuàng)建,被調(diào)用這個方法即可。
3. 子類通過 `objectForRowAtIndexPath` 方法可以快速獲取 item,不用重寫。
對照 demo(SHA-1:6475496),感受一下效果。
我們以之前所說的,代理協(xié)議中常用的兩個方法為例,看看怎么進行優(yōu)化與解耦。
首先是計算高度,這個邏輯并不一定在 C 完成,由于涉及到 UI,所以由 Cell 負責(zé)實現(xiàn)即可。而計算高度的依據(jù)就是 Object,所以我們給基類的 Cell 加上一個類方法:
+ (CGFloat)tableView:(UITableView*)tableView rowHeightForObject:(KtTableViewBaseItem *)object;
另外一類問題是以處理點擊事件為代表的代理方法, 它們的主要特點是都有 `indexPath` 參數(shù)用來表示位置。然而實際在處理過程中,我們并不關(guān)系位置,關(guān)心的是這個位置上的數(shù)據(jù)。
因此,我們對代理方法做一層封裝,使得 C 調(diào)用的方法中都是帶有數(shù)據(jù)參數(shù)的。因為這個數(shù)據(jù)對象可以從數(shù)據(jù)源拿到,所以我們需要能夠在代理方法中獲取到數(shù)據(jù)源對象。
為了實現(xiàn)這一點, 最好的辦法就是繼承 `UITableView`:
@protocol KtTableViewDelegate<UITableViewDelegate> @optional - (void)didSelectObject:(id)object atIndexPath:(NSIndexPath*)indexPath; - (UIView *)headerViewForSectionObject:(KtTableViewSectionObject *)sectionObject atSection:(NSInteger)section; // 將來可以有 cell 的編輯,交換,左滑等回調(diào) // 這個協(xié)議繼承了UITableViewDelegate ,所以自己做一層中轉(zhuǎn),VC 依然需要實現(xiàn)某 @end @interface KtBaseTableView : UITableView<UITableViewDelegate> @property (nonatomic, assign) id<KtTableViewDataSource> ktDataSource; @property (nonatomic, assign) id<KtTableViewDelegate> ktDelegate; @end
cell 高度的實現(xiàn)如下,調(diào)用數(shù)據(jù)源的方法獲取到數(shù)據(jù):
- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath { id<KtTableViewDataSource> dataSource = (id<KtTableViewDataSource>)tableView.dataSource; KtTableViewBaseItem *object = [dataSource tableView:tableView objectForRowAtIndexPath:indexPath]; Class cls = [dataSource tableView:tableView cellClassForObject:object]; return [cls tableView:tableView rowHeightForObject:object]; }
通過對 `UITableViewDelegate` 的封裝(其實主要是通過 `UITableView` 完成),我們獲得了以下特性:
1. C 不用關(guān)心 Cell 高度了,這個由每個 Cell 類自己負責(zé)
2. 如果數(shù)據(jù)本身存在數(shù)據(jù)源中,那么在代理協(xié)議中它可以被傳給 C,免去了 C 重新訪問數(shù)據(jù)源的操作。
3. 如果數(shù)據(jù)不存在于數(shù)據(jù)源,那么代理協(xié)議的方法會被正常轉(zhuǎn)發(fā)(因為自定義的代理協(xié)議繼承自 `UITableViewDelegate`)
對照 demo(SHA-1:ca9b261),感受一下效果。
在上面的兩次封裝中,其實我們是把 `UITableView` 持有原生的代理和數(shù)據(jù)源,改成了 `KtTableView` 持有自定義的代理和數(shù)據(jù)源。并且默認實現(xiàn)了很多系統(tǒng)的方法。
到目前為止,看上去一切都已經(jīng)完成了,然而實際上還是存在一些可以改進的地方:
1. 目前仍然不是 MVC 模式!
2. C 的邏輯和實現(xiàn)依然可以進一步簡化
基于以上考慮, 我們實現(xiàn)一個 `UIViewController` 的子類,并且把數(shù)據(jù)源和代理封裝到 C 中。
@interface KtTableViewController : UIViewController<KtTableViewDelegate, KtTableViewControllerDelegate> @property (nonatomic, strong) KtBaseTableView *tableView; @property (nonatomic, strong) KtTableViewDataSource *dataSource; @property (nonatomic, assign) UITableViewStyle tableViewStyle; // 用來創(chuàng)建 tableView - (instancetype)initWithStyle:(UITableViewStyle)style; @end
為了確保子類創(chuàng)建了數(shù)據(jù)源,我們把這個方法定義到協(xié)議里,并且定義為 `required`。
現(xiàn)在我們梳理一下經(jīng)過改造的 `TableView` 該怎么用:
1. 首先你需要創(chuàng)建一個繼承自 `KtTableViewController` 的視圖控制器,并且調(diào)用它的 `initWithStyle` 方法。
`objc KTMainViewController *mainVC = [[KTMainViewController alloc] initWithStyle:UITableViewStylePlain];`
2. 在子類 VC 中實現(xiàn) `createDataSource` 方法,實現(xiàn)數(shù)據(jù)源的綁定。
```objc
* (void)createDataSource { self.dataSource = [[KtMainTableViewDataSource alloc] init]; // 這 一步創(chuàng)建了數(shù)據(jù)源 } ```
3. 在數(shù)據(jù)源中,需要指定 cell 的類型。
```objc
* (Class)tableView:(UITableView *)tableView cellClassForObject:(KtTableViewBaseItem *)object { return [KtMainTableViewCell class]; } ```
4. 在 Cell 中,需要通過解析數(shù)據(jù),來更新 UI 并返回自己的高度。
objc
* (CGFloat)tableView:(UITableView *)tableView rowHeightForObject:(KtTableViewBaseItem *)object { return 60; } // Demo 中沿用了父類的 setObject 方法。 ```
到目前為止,我們實現(xiàn)了對 `UITableView` 以及相關(guān)協(xié)議、方法的封裝,使它更容易使用,避免了很多重復(fù)、無意義的代碼。
在使用時,我們需要創(chuàng)建一個控制器,一個數(shù)據(jù)源,一個自定義 Cell,它們正好是基于 MVC 模式的。因此,可以說在封裝與解耦方面,我們已經(jīng)做的相當(dāng)好了,即使再花大力氣,也很難有明顯的提高。
但關(guān)于 `UITableView` 的討論遠遠沒有結(jié)束,我列出了以下需要解決的問題
1. 在這種設(shè)計下,數(shù)據(jù)的回傳不夠方便,比如 cell 的給 C 發(fā)消息。
2. 下拉刷新與上拉加載如何集成
3. 網(wǎng)絡(luò)請求的發(fā)起,與解析數(shù)據(jù)如何集成
關(guān)于第一個問題,其實是普通的 MVC 模式中 V 和 C 的交互問題,可以在 Cell(或者其他類) 中添加 weak 屬性達到直接持有的目的,也可以定義協(xié)議。
問題二和三是另一大塊話題,網(wǎng)絡(luò)請求大家都會實現(xiàn),但如何優(yōu)雅的集成進框架,保證代碼的簡單和可拓展,就是一個值得深入思考,研究的問題了。接下來我們就重點討論網(wǎng)絡(luò)請求。
一個 iOS 的網(wǎng)絡(luò)層框架該如何設(shè)計?這是一個非常寬泛,也超出我能力范圍之外的問題。業(yè)內(nèi)已有一些優(yōu)秀的,成熟的思路和解決方案,由于能力,角色所限,我決定從一個普通開發(fā)者而不是架構(gòu)師的角度來說說,一個普通的、簡單的網(wǎng)絡(luò)層該如何設(shè)計。我相信再復(fù)雜的架構(gòu),也是由簡單的設(shè)計演化而來的。
對于絕大多數(shù)小型應(yīng)用來說,集成 `AFNetworking` 這樣的網(wǎng)絡(luò)請求框架就足以應(yīng)付 99% 以上的需求了。但是隨著項目的擴大,或者用長遠的眼光來考慮,直接在 VC 中調(diào)用具體的網(wǎng)絡(luò)框架(下面以 `AFNetworking` 為例),至少存在以下問題:
1. 一旦日后 `AFNetworking` 停止維護,而且我們需要更換網(wǎng)絡(luò)框架,這個成本將無法想象。所有的 VC 都要改動代碼,而且絕大多數(shù)改動都是雷同的。
這樣的例子真實存在,比如我們的項目中就依然使用早已停止維護的 `ASIHTTPRequest`,可以預(yù)見,這個框架遲早要被替換。
2. 現(xiàn)有的框架可能無法實現(xiàn)我們的需求。以 `ASIHTTPRequest` 為例,它的底層用 `NSOperation` 來表示每一個網(wǎng)絡(luò)請求。眾所周知,一個 `NSOperation` 的取消,并不是簡單調(diào)用 `cancel` 方法就可以的。在不修改源碼的前提下,一旦它被放入隊列,其實是無法取消的。
3. 有時候我們的需求僅僅是進行網(wǎng)絡(luò)請求,還會對這個請求進行各種自定義的拓展。比如我們可能要統(tǒng)計請求的發(fā)起和結(jié)束時間,從而計算網(wǎng)絡(luò)請求,數(shù)據(jù)解析的步驟的耗時。有時候,我們希望設(shè)計一個通用組件,并且支持由各個業(yè)務(wù)部門去自定義具體的規(guī)則。比如可能不同的部門,會為 HTTP 請求添加不同的頭部。
4. 網(wǎng)絡(luò)請求還有可能有其他廣泛需要添加的需求,比如請求失敗時的彈窗,請求時的日志記錄等等。
參考當(dāng)前代碼(SHA-1:a55ef42)感受一下沒有任何網(wǎng)絡(luò)層時的設(shè)計。
其實解決方案非常簡單:
所有的計算機問題,都可以通過添加中間層來解決
讀者可以自行思考,為什么添加中間層可以解決上述三個問題。
對于一個網(wǎng)絡(luò)框架來說,我認為主要有三個方面值得去設(shè)計:
1. 如何請求
2. 如何回調(diào)
3. 數(shù)據(jù)解析
一個完整的網(wǎng)絡(luò)請求一般由以上三個模塊組成,我們逐一分析每個模塊實現(xiàn)時的注意事項:
### 發(fā)起請求
發(fā)起請求時,一般有兩種思路,第一種是把所有要配置的參數(shù)寫到同一個方法中,借用 [與時俱進,HTTP/2下的iOS網(wǎng)絡(luò)層架構(gòu)設(shè)計](http://www.jianshu.com/p/a9bca62d8dab) 一文中的代碼表示:
+ (void)networkTransferWithURLString:(NSString *)urlString andParameters:(NSDictionary *)parameters isPOST:(BOOL)isPost transferType:(NETWORK_TRANSFER_TYPE)transferType andSuccessHandler:(void (^)(id responseObject))successHandler andFailureHandler:(void (^)(NSError *error))failureHandler { // 封裝AFN }
這種寫法的好處在于所有參數(shù)一目了然,而且簡單易用,每次都調(diào)用這個方法即可。但是缺點也很明顯,隨著參數(shù)和調(diào)用次數(shù)的增多,網(wǎng)絡(luò)請求的代碼很快多到爆炸。
另一組方法則是將 API 設(shè)置成一個對象,把要傳入的參數(shù)作為這個對象的屬性。在發(fā)起請求時,只要設(shè)置好對象的相關(guān)屬性,然后調(diào)用一個簡單的方法即可。
@interface DRDBaseAPI : NSObject @property (nonatomic, copy, nullable) NSString *baseUrl; @property (nonatomic, copy, nullable) void (^apiCompletionHandler)(_Nonnull id responseObject, NSError * _Nullable error); - (void)start; - (void)cancel; ... @end
根據(jù)前文提到的 Model 和 Item 的概念,那么應(yīng)該可以想到:**這個用于訪問網(wǎng)絡(luò)的 API 對象,其實是作為 Model 的一個屬性**。
Model 負責(zé)對外暴露必要的屬性和方法,而具體的網(wǎng)絡(luò)請求則由 API 對象完成,同時 Model 也應(yīng)該持有真正用來存儲數(shù)據(jù)的 Item。
一次網(wǎng)絡(luò)請求的返回結(jié)果應(yīng)該是一個 JSON 格式的字符串,通過系統(tǒng)的或者一些開源框架可以將它轉(zhuǎn)換成字典。
接下來我們需要使用 runtime 相關(guān)的方法,將字典轉(zhuǎn)換成 Item 對象。
最后,Model 需要將這個 Item 賦值給自己的屬性,從而完成整個網(wǎng)絡(luò)請求。
如果從全局角度來說,我們還需要一個 Model 請求完成的回調(diào),這樣 VC 才能有機會做相應(yīng)的處理。
考慮到 Block 和 Delegate 的優(yōu)缺點,我們選擇用 Block 來完成回調(diào)。
這一部分主要是利用 runtime 將字典轉(zhuǎn)換成 Item,它的實現(xiàn)并不算難,但是如何隱藏好實現(xiàn)細節(jié),使上層業(yè)務(wù)不用過多關(guān)心,則是我們需要考慮的問題。
我們可以定義一個基類的 Item,并且為它定義一個 `parseData` 函數(shù):
// KtBaseItem.m - (void)parseData:(NSDictionary *)data { // 解析 data 這個字典,為自己的屬性賦值 // 具體的實現(xiàn)請見后面的文章 }
首先,我們封裝一個 `KtBaseServerAPI` 對象,這個對象的主要目的有三個:
1. 隔離具體的網(wǎng)絡(luò)庫的實現(xiàn)細節(jié),為上層提供一個穩(wěn)定的的接口
2. 可以自定義一些屬性,比如網(wǎng)絡(luò)請求的狀態(tài),返回的數(shù)據(jù)等,方便的調(diào)用
3. 處理一些公用的邏輯,比如網(wǎng)絡(luò)耗時統(tǒng)計
具體的實現(xiàn)請參考 Git 提交歷史:SHA-1:76487f7
Model 主要需要負責(zé)發(fā)起網(wǎng)絡(luò)請求,并且處理回調(diào),來看一下基類的 Model 如何定義:
@interface KtBaseModel // 請求回調(diào) @property (nonatomic, copy) KtModelBlock completionBlock; //網(wǎng)絡(luò)請求 @property (nonatomic,retain) KtBaseServerAPI *serverApi; //網(wǎng)絡(luò)請求參數(shù) @property (nonatomic,retain) NSDictionary *params; //請求地址 需要在子類init中初始化 @property (nonatomic,copy) NSString *address; //model緩存 @property (retain,nonatomic) KtCache *ktCache;
它通過持有 API 對象完成網(wǎng)絡(luò)請求,可以定制自己的存儲邏輯,控制請求方式的選擇(長、短鏈接,JSON或protobuf)。
Model 應(yīng)該對上層暴露一個非常簡單的調(diào)用接口,因為假設(shè)一個 Model 對應(yīng)一個 URL,其實每次請求只需要設(shè)置好參數(shù),就可以調(diào)用合適的方法發(fā)起請求了。
由于我們不能預(yù)知請求何時結(jié)束,所以需要設(shè)置請求完成時的回調(diào),這也需要作為 Model 的一個屬性。
基類的 Item 主要是負責(zé) property name 到 json path 的映設(shè),以及 json 數(shù)據(jù)的解析。最核心的字典轉(zhuǎn)模型實現(xiàn)如下:
- (void)parseData:(NSDictionary *)data { Class cls = [self class]; while (cls != [KtBaseItem class]) { NSDictionary *propertyList = [[KtClassHelper sharedInstance] propertyList:cls]; for (NSString *key in [propertyList allKeys]) { NSString *typeString = [propertyList objectForKey:key]; NSString* path = [self.jsonDataMap objectForKey:key]; id value = [data objectAtPath:path]; [self setfieldName:key fieldClassName:typeString value:value]; } cls = class_getSuperclass(cls); } }
完整代碼參考 Git 提交歷史:SHA-1:77c6392
在實際使用時,首先要創(chuàng)建子類的 Modle 和 Item。子類的 Model 應(yīng)該持有 Item 對象,并且在網(wǎng)絡(luò)請求回調(diào)時,將 API 中攜帶的 JSON 數(shù)據(jù)賦值給 Item 對象。
這個 JSON 轉(zhuǎn)對象的過程在基類的 Item 中實現(xiàn),子類的 Item 在創(chuàng)建時,需要指定屬性名和 JSON 路徑之間的對應(yīng)關(guān)系。
對于上層來說,它需要生成一個 Model 對象,設(shè)置好它的路徑以及回調(diào),這個回調(diào)一般是網(wǎng)絡(luò)請求返回時 VC 的操作,比如調(diào)用 `reloadData` 方法。這時候的 VC 可以確定,網(wǎng)絡(luò)請求的數(shù)據(jù)就存在 Model 持有的 Item 對象中。
具體代碼參考 Git 提交歷史:SHA-1:8981e28
很多應(yīng)用的 `UITableview` 都具有下拉刷新和上拉加載的功能,在實現(xiàn)這個功能時,我們主要考慮兩點:
1. 隱藏底層的實現(xiàn)細節(jié),對外暴露穩(wěn)定易用的接口
2. Model 和 Item 如何實現(xiàn)
第一點已經(jīng)是老生常談,參考 SHA-1 61ba974 就可以看到如何實現(xiàn)一個簡單的封裝。
重點在于對于 Model 和 Item 的改造。
這個 Item 沒有什么別的作用,就是定義了一個屬性 `pageNumber`,這是需要與服務(wù)端協(xié)商的。Model 將會根據(jù)這個屬性這個屬性判斷有沒有全部加載完。
// In .h @interface KtBaseListItem : KtBaseItem @property (nonatomic, assign) int pageNumber; @end // In .m - (id)initWithData:(NSDictionary *)data { if (self = [super initWithData:data]) { self.pageNumber = [[NSString stringWithFormat:@"%@", [data objectForKey:@"page_number"]] intValue]; } return self; }
對于 Server 來說,如果每次都返回 `page_number` 無疑是非常低效的,因為每次參數(shù)都可能不同,計算總數(shù)據(jù)量是一項非常耗時的工作。因此在實際使用中,客戶端可以和 Server 約定,返回的結(jié)果中帶有 `isHasNext` 字段。通過這個字段,我們一樣可以判斷是否加載到最后一頁。
它持有一個 `ListItem` 對象, 對外暴露一組加載方法,并且定義了一個協(xié)議 `KtBaseListModelProtocol`,這個協(xié)議中的方法是請求結(jié)束后將要執(zhí)行的方法。
@protocol KtBaseListModelProtocol <NSObject> @required - (void)refreshRequestDidSuccess; - (void)loadRequestDidSuccess; - (void)didLoadLastPage; - (void)handleAfterRequestFinish; // 請求結(jié)束后的操作,刷新tableview或關(guān)閉動畫等。 @optional - (void)didLoadFirstPage; @end @interface KtBaseListModel : KtBaseModel @property (nonatomic, strong) KtBaseListItem *listItem; @property (nonatomic, weak) id<KtBaseListModelProtocol> delegate; @property (nonatomic, assign) BOOL isRefresh; // 如果為是,表示刷新,否則為加載。 - (void)loadPage:(int)pageNumber; - (void)loadNextPage; - (void)loadPreviousPage; @end
實際上,當(dāng) Server 端發(fā)生數(shù)據(jù)的增刪時,只傳 `nextPage` 這個參數(shù)是不能滿足要求的。兩次獲取的頁面并非完全沒有交集,很有可能他們具有重復(fù)元素,所以 Model 還應(yīng)該肩負起去重的任務(wù)。為了簡化問題,這里就不完整實現(xiàn)了。
它實現(xiàn)了 `ListMode` 中定義的協(xié)議,提供了一些通用的方法,而具體的業(yè)務(wù)邏輯則由子類實現(xiàn)。
#pragma -mark KtBaseListModelProtocol - (void)loadRequestDidSuccess { [self requestDidSuccess]; } - (void)refreshRequestDidSuccess { [self.dataSource clearAllItems]; [self requestDidSuccess]; } - (void)handleAfterRequestFinish { [self.tableView stopRefreshingAnimation]; [self.tableView reloadData]; } - (void)didLoadLastPage { [self.tableView.mj_footer endRefreshingWithNoMoreData]; } #pragma -mark KtTableViewDelegate - (void)pullUpToRefreshAction { [self.listModel loadNextPage]; } - (void)pullDownToRefreshAction { [self.listModel refresh]; }
在一個 VC 中,它只需要繼承 `RefreshTableViewController`,然后實現(xiàn) `requestDidSuccess` 方法即可。下面展示一下 VC 的完整代碼,它超乎尋常的簡單:
- (void)viewDidLoad { [super viewDidLoad]; [self createModel]; // Do any additional setup after loading the view, typically from a nib. } - (void)createModel { self.listModel = [[KtMainTableModel alloc] initWithAddress:@"/mooclist.php"]; self.listModel.delegate = self; } - (void)createDataSource { self.dataSource = [[KtMainTableViewDataSource alloc] init]; // 這一步創(chuàng)建了數(shù)據(jù)源 } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)requestDidSuccess { for (KtMainTableBookItem *book in ((KtMainTableModel *)self.listModel).tableViewItem.books) { KtTableViewBaseItem *item = [[KtTableViewBaseItem alloc] init]; item.itemTitle = book.bookTitle; [self.dataSource appendItem:item]; } }
其他的判斷,比如請求結(jié)束時關(guān)閉動畫,最后一頁提示沒有更多數(shù)據(jù),下拉刷新和上拉加載觸發(fā)的方法等公共邏輯已經(jīng)被父類實現(xiàn)了。
到此,關(guān)于“怎么寫好一個UITableView”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
新聞名稱:怎么寫好一個UITableView
文章出自:http://www.rwnh.cn/article28/gcgejp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供定制網(wǎng)站、網(wǎng)站收錄、網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計公司、網(wǎng)站導(dǎo)航、搜索引擎優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)