中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么寫好一個UITableView

發布時間:2021-11-15 12:49:00 來源:億速云 閱讀:153 作者:iii 欄目:移動開發

這篇文章主要介紹“怎么寫好一個UITableView”,在日常操作中,相信很多人在怎么寫好一個UITableView問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”怎么寫好一個UITableView”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

如果你覺得 `UITableViewDelegate` 和 `UITableViewDataSource` 這兩個協議中有大量方法每次都是復制粘貼,實現起來大同小異;如果你覺得發起網絡請求并解析數據需要一大段代碼,加上刷新和加載后簡直復雜度爆表,如果你想知道為什么下面的代碼可以滿足上述所有要求:

怎么寫好一個UITableView

MVC

在討論解耦之前,我們要弄明白 MVC 的核心:控制器(以下簡稱 C)負責模型(以下簡稱 M)和視圖(以下簡稱 V)的交互。

這里所說的 M,通常不是一個單獨的類,很多情況下它是由多個類構成的一個層。最上層的通常是以 `Model` 結尾的類,它直接被 C 持有。`Model` 類還可以持有兩個對象:

1.  Item:它是實際存儲數據的對象。它可以理解為一個字典,和 V 中的屬性一一對應

2.  Cache:它可以緩存自己的 Item(如果有很多)

常見的誤區:

1.  一般情況下數據的處理會放在 M 而不是 C(C 只做不能復用的事)

2.  解耦不只是把一段代碼拿到外面去。而是關注是否能合并重復代碼, 并且有良好的拖展性。

原始版

在 C 中,我們創建 `UITableView` 對象,然后將它的數據源和代理設置為自己。也就是自己管理著 UI 邏輯和數據存取的邏輯。在這種架構下,主要存在這些問題:

1.  違背 MVC 模式,現在是 V 持有 C 和 M。

2.  C 管理了全部邏輯,耦合太嚴重。

3.  其實絕大多數 UI 相關都是由 Cell 而不是 `UITableView` 自身完成的。

為了解決這些問題,我們首先弄明白,數據源和代理分別做了那些事。

數據源

它有兩個必須實現的代理方法:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

簡單來說,只要實現了這個兩個方法,一個簡單的 `UITableView` 對象就算是完成了。

除此以外,它還負責管理 `section` 的數量,標題,某一個 `cell` 的編輯和移動等。

代理

代理主要涉及以下幾個方面的內容:

1.  cell、headerView 等展示前、后的回調。

2.  cell、headerView 等的高度,點擊事件。

最常用的也是兩個方法:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;

提醒:絕大多數代理方法都有一個 `indexPath` 參數

優化數據源

最簡單的思路是單獨把數據源拿出來作為一個對象。

這種寫法有一定的解耦作用,同時可以有效減少 C 中的代碼量。然而總代碼量會上升。我們的目標是減少不必要的代碼。

比如獲取每一個 `section` 的行數,它的實現邏輯總是高度類似。然而由于數據源的具體實現方式不統一,所以每個數據源都要重新實現一遍。

SectionObject

首先我們來思考一個問題,數據源作為 M,它持有的 Item 長什么樣?答案是一個二維數組,每個元素保存了一個 `section` 所需要的全部信息。因此除了有自己的數組(給cell用)外,還有 section 的標題等,我們把這樣的元素命名為 `SectionObject`:

@interface KtTableViewSectionObject : NSObject
@property (nonatomic, copy) NSString *headerTitle; // UITableDataSource 協議中的 titleForHeaderInSection 方法可能會用到
@property (nonatomic, copy) NSString *footerTitle; // UITableDataSource 協議中的 titleForFooterInSection 方法可能會用到
@property (nonatomic, retain) NSMutableArray *items;
- (instancetype)initWithItemArray:(NSMutableArray *)items;
@end

Item

其中的 `items` 數組,應該存儲了每個 cell 所需要的 `Item`,考慮到 `Cell` 的特點,基類的 `BaseItem` 可以設計成這樣:

@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

父類實現代碼

規定好了統一的數據存儲格式以后,我們就可以考慮在基類中完成某些方法了。以 `- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section` 方法為例,它可以這樣實現:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (self.sections.count > section) {
        KtTableViewSectionObject *sectionObject = [self.sections objectAtIndex:section];
        return sectionObject.items.count;
    }
    return 0;
}

比較困難的是創建 `cell`,因為我們不知道 `cell` 的類型,自然也就無法調用 `alloc` 方法。除此以外,`cell` 除了創建,還需要設置 UI,這些都是數據源不應該做的事。

這兩個問題的解決方案如下:

1.  定義一個協議,父類返回基類 `Cell`,子類視情況返回合適的類型。

2.  為 `Cell` 添加一個 `setObject` 方法,用于解析 Item 并更新 UI。

優勢

經過這一番折騰,好處是相當明顯的:

1.  子類的數據源只需要實現 `cellClassForObject` 方法即可。原來的數據源方法已經在父類中被統一實現了。

2.  每一個 Cell 只要寫好自己的 `setObject` 方法,然后坐等自己被創建,被調用這個方法即可。

3.  子類通過 `objectForRowAtIndexPath` 方法可以快速獲取 item,不用重寫。

對照 demo(SHA-1:6475496),感受一下效果。

優化代理

我們以之前所說的,代理協議中常用的兩個方法為例,看看怎么進行優化與解耦。

首先是計算高度,這個邏輯并不一定在 C 完成,由于涉及到 UI,所以由 Cell 負責實現即可。而計算高度的依據就是 Object,所以我們給基類的 Cell 加上一個類方法:

+ (CGFloat)tableView:(UITableView*)tableView rowHeightForObject:(KtTableViewBaseItem *)object;

另外一類問題是以處理點擊事件為代表的代理方法, 它們的主要特點是都有 `indexPath` 參數用來表示位置。然而實際在處理過程中,我們并不關系位置,關心的是這個位置上的數據。

因此,我們對代理方法做一層封裝,使得 C 調用的方法中都是帶有數據參數的。因為這個數據對象可以從數據源拿到,所以我們需要能夠在代理方法中獲取到數據源對象。

為了實現這一點, 最好的辦法就是繼承 `UITableView`:

@protocol KtTableViewDelegate<UITableViewDelegate>
@optional
- (void)didSelectObject:(id)object atIndexPath:(NSIndexPath*)indexPath;
- (UIView *)headerViewForSectionObject:(KtTableViewSectionObject *)sectionObject atSection:(NSInteger)section;
// 將來可以有 cell 的編輯,交換,左滑等回調
// 這個協議繼承了UITableViewDelegate ,所以自己做一層中轉,VC 依然需要實現某
@end
@interface KtBaseTableView : UITableView<UITableViewDelegate>
@property (nonatomic, assign) id<KtTableViewDataSource> ktDataSource;
@property (nonatomic, assign) id<KtTableViewDelegate> ktDelegate;
@end

cell 高度的實現如下,調用數據源的方法獲取到數據:

- (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 不用關心 Cell 高度了,這個由每個 Cell 類自己負責

2.  如果數據本身存在數據源中,那么在代理協議中它可以被傳給 C,免去了 C 重新訪問數據源的操作。

3.  如果數據不存在于數據源,那么代理協議的方法會被正常轉發(因為自定義的代理協議繼承自 `UITableViewDelegate`)

對照 demo(SHA-1:ca9b261),感受一下效果。

 更加 MVC,更加簡潔

在上面的兩次封裝中,其實我們是把 `UITableView` 持有原生的代理和數據源,改成了 `KtTableView` 持有自定義的代理和數據源。并且默認實現了很多系統的方法。

到目前為止,看上去一切都已經完成了,然而實際上還是存在一些可以改進的地方:

1.  目前仍然不是 MVC 模式!

2.  C 的邏輯和實現依然可以進一步簡化

基于以上考慮, 我們實現一個 `UIViewController` 的子類,并且把數據源和代理封裝到 C 中。

@interface KtTableViewController : UIViewController<KtTableViewDelegate, KtTableViewControllerDelegate>
@property (nonatomic, strong) KtBaseTableView *tableView;
@property (nonatomic, strong) KtTableViewDataSource *dataSource;
@property (nonatomic, assign) UITableViewStyle tableViewStyle; // 用來創建 tableView
- (instancetype)initWithStyle:(UITableViewStyle)style;
@end

為了確保子類創建了數據源,我們把這個方法定義到協議里,并且定義為 `required`。

成果與目標

現在我們梳理一下經過改造的 `TableView` 該怎么用:

1.  首先你需要創建一個繼承自 `KtTableViewController` 的視圖控制器,并且調用它的 `initWithStyle` 方法。

    `objc KTMainViewController *mainVC = [[KTMainViewController alloc] initWithStyle:UITableViewStylePlain];`

2.  在子類 VC 中實現 `createDataSource` 方法,實現數據源的綁定。

    ```objc

    *   (void)createDataSource { self.dataSource = [[KtMainTableViewDataSource alloc] init]; // 這 一步創建了數據源 } ```

3.  在數據源中,需要指定 cell 的類型。

    ```objc

    *   (Class)tableView:(UITableView *)tableView cellClassForObject:(KtTableViewBaseItem *)object { return [KtMainTableViewCell class]; } ```

4.  在 Cell 中,需要通過解析數據,來更新 UI 并返回自己的高度。

    objc

    *   (CGFloat)tableView:(UITableView *)tableView rowHeightForObject:(KtTableViewBaseItem *)object { return 60; } // Demo 中沿用了父類的 setObject 方法。 ```

還有什么要優化的

到目前為止,我們實現了對 `UITableView` 以及相關協議、方法的封裝,使它更容易使用,避免了很多重復、無意義的代碼。

在使用時,我們需要創建一個控制器,一個數據源,一個自定義 Cell,它們正好是基于 MVC 模式的。因此,可以說在封裝與解耦方面,我們已經做的相當好了,即使再花大力氣,也很難有明顯的提高。

但關于 `UITableView` 的討論遠遠沒有結束,我列出了以下需要解決的問題

1.  在這種設計下,數據的回傳不夠方便,比如 cell 的給 C 發消息。

2.  下拉刷新與上拉加載如何集成

3.  網絡請求的發起,與解析數據如何集成

關于第一個問題,其實是普通的 MVC 模式中 V 和 C 的交互問題,可以在 Cell(或者其他類) 中添加 weak 屬性達到直接持有的目的,也可以定義協議。

問題二和三是另一大塊話題,網絡請求大家都會實現,但如何優雅的集成進框架,保證代碼的簡單和可拓展,就是一個值得深入思考,研究的問題了。接下來我們就重點討論網絡請求。

為何創建網絡層

一個 iOS 的網絡層框架該如何設計?這是一個非常寬泛,也超出我能力范圍之外的問題。業內已有一些優秀的,成熟的思路和解決方案,由于能力,角色所限,我決定從一個普通開發者而不是架構師的角度來說說,一個普通的、簡單的網絡層該如何設計。我相信再復雜的架構,也是由簡單的設計演化而來的。

對于絕大多數小型應用來說,集成 `AFNetworking` 這樣的網絡請求框架就足以應付 99% 以上的需求了。但是隨著項目的擴大,或者用長遠的眼光來考慮,直接在 VC 中調用具體的網絡框架(下面以 `AFNetworking` 為例),至少存在以下問題:

1.  一旦日后 `AFNetworking` 停止維護,而且我們需要更換網絡框架,這個成本將無法想象。所有的 VC 都要改動代碼,而且絕大多數改動都是雷同的。

    這樣的例子真實存在,比如我們的項目中就依然使用早已停止維護的 `ASIHTTPRequest`,可以預見,這個框架遲早要被替換。

2.  現有的框架可能無法實現我們的需求。以 `ASIHTTPRequest` 為例,它的底層用 `NSOperation` 來表示每一個網絡請求。眾所周知,一個 `NSOperation` 的取消,并不是簡單調用 `cancel` 方法就可以的。在不修改源碼的前提下,一旦它被放入隊列,其實是無法取消的。

3.  有時候我們的需求僅僅是進行網絡請求,還會對這個請求進行各種自定義的拓展。比如我們可能要統計請求的發起和結束時間,從而計算網絡請求,數據解析的步驟的耗時。有時候,我們希望設計一個通用組件,并且支持由各個業務部門去自定義具體的規則。比如可能不同的部門,會為 HTTP 請求添加不同的頭部。

4.  網絡請求還有可能有其他廣泛需要添加的需求,比如請求失敗時的彈窗,請求時的日志記錄等等。

參考當前代碼(SHA-1:a55ef42)感受一下沒有任何網絡層時的設計。

如何設計網絡層

其實解決方案非常簡單:

所有的計算機問題,都可以通過添加中間層來解決

讀者可以自行思考,為什么添加中間層可以解決上述三個問題。

三大模塊

對于一個網絡框架來說,我認為主要有三個方面值得去設計:

1.  如何請求

2.  如何回調

3.  數據解析

一個完整的網絡請求一般由以上三個模塊組成,我們逐一分析每個模塊實現時的注意事項:

### 發起請求

發起請求時,一般有兩種思路,第一種是把所有要配置的參數寫到同一個方法中,借用 [與時俱進,HTTP/2下的iOS網絡層架構設計](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
                   }

這種寫法的好處在于所有參數一目了然,而且簡單易用,每次都調用這個方法即可。但是缺點也很明顯,隨著參數和調用次數的增多,網絡請求的代碼很快多到爆炸。

另一組方法則是將 API 設置成一個對象,把要傳入的參數作為這個對象的屬性。在發起請求時,只要設置好對象的相關屬性,然后調用一個簡單的方法即可。

@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

根據前文提到的 Model 和 Item 的概念,那么應該可以想到:**這個用于訪問網絡的 API 對象,其實是作為 Model 的一個屬性**。

Model 負責對外暴露必要的屬性和方法,而具體的網絡請求則由 API 對象完成,同時 Model 也應該持有真正用來存儲數據的 Item。

如何回調

一次網絡請求的返回結果應該是一個 JSON 格式的字符串,通過系統的或者一些開源框架可以將它轉換成字典。

接下來我們需要使用 runtime 相關的方法,將字典轉換成 Item 對象。

最后,Model 需要將這個 Item 賦值給自己的屬性,從而完成整個網絡請求。

如果從全局角度來說,我們還需要一個 Model 請求完成的回調,這樣 VC 才能有機會做相應的處理。

考慮到 Block 和 Delegate 的優缺點,我們選擇用 Block 來完成回調。

[數據解析](https://bestswifter.com/how-to-create-an-uitableview/#)

這一部分主要是利用 runtime 將字典轉換成 Item,它的實現并不算難,但是如何隱藏好實現細節,使上層業務不用過多關心,則是我們需要考慮的問題。

我們可以定義一個基類的 Item,并且為它定義一個 `parseData` 函數:

// KtBaseItem.m
- (void)parseData:(NSDictionary *)data {
    // 解析 data 這個字典,為自己的屬性賦值
    // 具體的實現請見后面的文章
}

封裝 API 對象

首先,我們封裝一個 `KtBaseServerAPI` 對象,這個對象的主要目的有三個:

1.  隔離具體的網絡庫的實現細節,為上層提供一個穩定的的接口

2.  可以自定義一些屬性,比如網絡請求的狀態,返回的數據等,方便的調用

3.  處理一些公用的邏輯,比如網絡耗時統計

具體的實現請參考 Git 提交歷史:SHA-1:76487f7

Model 與 Item

BaseModel

Model 主要需要負責發起網絡請求,并且處理回調,來看一下基類的 Model 如何定義:

@interface KtBaseModel
// 請求回調
@property (nonatomic, copy) KtModelBlock completionBlock;
//網絡請求
@property (nonatomic,retain) KtBaseServerAPI *serverApi;
//網絡請求參數
@property (nonatomic,retain) NSDictionary *params;
//請求地址 需要在子類init中初始化
@property (nonatomic,copy)   NSString *address;
//model緩存
@property (retain,nonatomic) KtCache *ktCache;

它通過持有 API 對象完成網絡請求,可以定制自己的存儲邏輯,控制請求方式的選擇(長、短鏈接,JSON或protobuf)。

Model 應該對上層暴露一個非常簡單的調用接口,因為假設一個 Model 對應一個 URL,其實每次請求只需要設置好參數,就可以調用合適的方法發起請求了。

由于我們不能預知請求何時結束,所以需要設置請求完成時的回調,這也需要作為 Model 的一個屬性。

BaseItem

基類的 Item 主要是負責 property name 到 json path 的映設,以及 json 數據的解析。最核心的字典轉模型實現如下:

- (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

如何使用

在實際使用時,首先要創建子類的 Modle 和 Item。子類的 Model 應該持有 Item 對象,并且在網絡請求回調時,將 API 中攜帶的 JSON 數據賦值給 Item 對象。

這個 JSON 轉對象的過程在基類的 Item 中實現,子類的 Item 在創建時,需要指定屬性名和 JSON 路徑之間的對應關系。

對于上層來說,它需要生成一個 Model 對象,設置好它的路徑以及回調,這個回調一般是網絡請求返回時 VC 的操作,比如調用 `reloadData` 方法。這時候的 VC 可以確定,網絡請求的數據就存在 Model 持有的 Item 對象中。

具體代碼參考 Git 提交歷史:SHA-1:8981e28

下拉刷新

很多應用的 `UITableview` 都具有下拉刷新和上拉加載的功能,在實現這個功能時,我們主要考慮兩點:

1.  隱藏底層的實現細節,對外暴露穩定易用的接口

2.  Model 和 Item 如何實現

第一點已經是老生常談,參考 SHA-1 61ba974 就可以看到如何實現一個簡單的封裝。

重點在于對于 Model 和 Item 的改造。

ListItem

這個 Item 沒有什么別的作用,就是定義了一個屬性 `pageNumber`,這是需要與服務端協商的。Model 將會根據這個屬性這個屬性判斷有沒有全部加載完。

// 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` 無疑是非常低效的,因為每次參數都可能不同,計算總數據量是一項非常耗時的工作。因此在實際使用中,客戶端可以和 Server 約定,返回的結果中帶有 `isHasNext` 字段。通過這個字段,我們一樣可以判斷是否加載到最后一頁。

ListModel

它持有一個 `ListItem` 對象, 對外暴露一組加載方法,并且定義了一個協議 `KtBaseListModelProtocol`,這個協議中的方法是請求結束后將要執行的方法。

@protocol KtBaseListModelProtocol <NSObject>
@required
- (void)refreshRequestDidSuccess;
- (void)loadRequestDidSuccess;
- (void)didLoadLastPage;
- (void)handleAfterRequestFinish; // 請求結束后的操作,刷新tableview或關閉動畫等。
@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

實際上,當 Server 端發生數據的增刪時,只傳 `nextPage` 這個參數是不能滿足要求的。兩次獲取的頁面并非完全沒有交集,很有可能他們具有重復元素,所以 Model 還應該肩負起去重的任務。為了簡化問題,這里就不完整實現了。

RefreshTableViewController

它實現了 `ListMode` 中定義的協議,提供了一些通用的方法,而具體的業務邏輯則由子類實現。

#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`,然后實現 `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]; // 這一步創建了數據源
}
- (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];
    }
}

其他的判斷,比如請求結束時關閉動畫,最后一頁提示沒有更多數據,下拉刷新和上拉加載觸發的方法等公共邏輯已經被父類實現了。

到此,關于“怎么寫好一個UITableView”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

大竹县| 普兰县| 琼结县| 美姑县| 娱乐| 昭通市| 理塘县| 昆明市| 淅川县| 张家口市| 饶平县| 绍兴县| 湘乡市| 乌拉特后旗| 乌拉特中旗| 建德市| 南通市| 三门县| 兴化市| 远安县| 金乡县| 鹤庆县| 崇阳县| 舞阳县| 苏州市| 辉县市| 睢宁县| 夏河县| 开原市| 元朗区| 宣威市| 建昌县| 灵台县| 利川市| 固安县| 武清区| 万宁市| 金寨县| 仪陇县| 改则县| 容城县|