iOS tableView中的MVC、MVVM

JonPai發表於2019-02-06

開頭:最近在利用有道的api嘗試做一個翻譯的應用,其中用到了tableview。有一段時間沒有接觸這個常用UI,發現該忘的都忘了哈哈。

本文不著重講述tableView的各種基本使用了,而打算通過下面幾個方面來進行敘述思考。在複習tableView的同時,想思考一下程式碼的規範問題。

  • 1.tableView下中MVC思考
  • 2.tableView與自定義cell
  • 3.一個tableView,多種型別cell(MVVM模式)

1.tableView中MVC思考

先貼上一張MVC的一張大圖(給自己看就好)

mvc.jpeg
controller 相當於媒介,幫助model和View建立其聯絡。道理我都懂,但是以往在coding的時候,往往會出現以下的情況(程式碼不看):


    -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    TeacherHomeworkListCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TeacherHomeworkListCell" forIndexPath:indexPath];
    cell.delegate = self;
    NSArray *arr = homeworkListModelTDArray[indexPath.section];
    cell.model = arr[indexPath.row];
    if ([cell.model.requirements isEqualToString:@"0"]) {
        cell.operateView.hidden = YES;
        cell.correctHomeworkButton.hidden = YES;
        cell.operateViewNR.hidden = NO;
    } else {
        cell.operateView.hidden = NO;
        
        .......
複製程式碼

貼上這麼多以前的程式碼主要的,是可以看出在以往coding過程中,我又容易忽視了mvc的設計思想。這可能會導致:

  • 1.將view(cell)和model(資料)在controller中建立起了直接的聯絡。
  • 2.又會讓tableView顯得龐然大物。

因此在這次coding 的過程中,我時不時有意地注意到了這個問題,將所有的與UI展示有關的資料交由View下來處理,而避免在controller中直接的設定。所以這次我的設定資料來源的方法圖片如下:

iOS tableView中的MVC、MVVM

以往在設計一個tableView中有兩種以上不同cell時候,我容易在cellForRowAtIndexPath:這個方法中作出許許多多的if-else的判斷,而現在我將這個判斷交由cell自己來做,controller自己不需要知道cell是要什麼型別,而只要將得到的cell展示即可。

我們需要做的,就是傳入需要的資料,可以indexPath也可以是model等。 這可能也很好的貫徹了“依賴注入”的設計原則。

同時,這可能對於後面的關於:自定義行高,一個tableView中有兩個cell等提供有益的幫助。

不過白貓黑貓,能抓到老鼠的的都是好貓哈哈哈。

補充:注意一定要傳入tableView,因為cell的複用問題。


2.tableView與自定義cell

這部分寫給自己看哈哈哈,主要是遇到了幾個忘記的知識點。

  • 1.如果事先給tableView註冊了cell型別,則不需要進行cell為空的處理,快取池中就會有足夠的cell等待複用。
  • 2.在通過[_tableView registerClass...]註冊了cell之後,在進行cell複用時,底層是呼叫了 - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier的方法,因此如果需要返回xib自定義的cell,則需要對這個方法override。
  • 3.在通過xib自定義時,需要通過tableViewDelegate設定行高(代理裡的預設行高為44),否則可能無法完全展示xib的cell。
  • 4.通過xib載入cell時候,底層會呼叫awakeFromNib的方法(這個也忘了?)

3.一個tableView,多種型別cell

在現如今的應用程式當中,一個tableView中含有多種型別的cell,這已是一個普遍的UI需求。

參考了網上各種方法之後,自我總結了一下。 我想到了如下兩種:

  • 1.將相關引數傳入view,通過view來返回需要指定型別的cell。
  • 2.根據兩種不同的識別符號,獲取不同的cell。

對於第一種方法:

我在實現的過程中,將view與xib繫結在一起,根據傳入的indexPath或者資料,返回出指定型別的的cell。以往我會在xib檔案中只放一個cell,而此次我則不再是通過[array lastObject]取出唯一的cell,而是根據指定的index從多個cell中取出指定的cell。

iOS tableView中的MVC、MVVM
附上偽碼:

@implementation CustomCell
- (instancetype)cellWithIndexPath:(NSIndexPath *)indexPath {
    CustomCell *cell = nil;
    NSArray *cellArr = [[NSBundle mainBundle] loadNibWith...];
    if (...){
        cell = [cellArr objectAtIndex:index];
    }else if (...) {
        ...
    }else {
        cell = [cellArr lastObject];
    }
    return cell;
}
@end
複製程式碼

但是這種方法的缺點也很明顯,與tag的使用大同小異。當一個tableView需要許多種型別的cell來豐富內容的時候,採用這種方法在開發過程中會帶來一定的混亂。

可以適當的採用enum列舉,但同時也要注意xib中左皮膚中檢視的上下位置。

例如上圖中,NormalTyepCell 的index=1,MeTypeCell為2,而當兩者調換了位置之後,索引也會發生改變。

對於第二種方法:

個人也比較傾向於第二種方法,對於不同的型別的cell,應有不同的Identifier進行繫結,這樣子比較科學哈哈,同時採用了較為高大上的MVVM,。

我們大可以像第一種方式一樣,從xib中取出所需要的cell,但是這也許就沒有多個Identifier存在的必要性。而針對不同識別符號取出對應的cell,更多的是依賴:

[tableView dequeueReusableCellWithIdentifier:Identifier]; 的呼叫。

如何獲得對應cell的Identifier:

同樣是可以採用前面的方法來進行開發,但是細想一下,如果cell的型別過多,這樣子會導致view中程式碼成本過高,相比與第一種方法,這種方法則更加麻煩。

如何解決?那就著手已產生的麻煩---胖View。

MVVM

開始寫文章的時候並沒有考慮,但是寫著寫著就覺得也許可以應用進來哈哈。9012希望能多寫文章,記錄筆記!

先貼幾張MVVM的大圖:

iOS tableView中的MVC、MVVM

iOS tableView中的MVC、MVVM
iOS tableView中的MVC、MVVM

對於新面孔ViewModel:從MVC的controller中抽取出來的展示邏輯,負責從model中獲取view所需的資料,轉換成View可以展示的資料,並暴露公開的屬性和命令供view進行繫結。

回到第二種方法的實現上來。

MVVM的採用可以很好的為view以及controller制定了瘦身的計劃,我們將“獲取指定cell的Identifier”這一邏輯交由ViewModel來實現,然後通過controller告訴view需要哪個cell。

附上viewModel和tableView設定資料來源的偽碼:

@implementation ViewModel 
- (NSString *)identifierWith:(Model *)model {
    if (model.type == ?){
        return firstCellID;
    }else if (model ...){
        return secondCellID;
    }else {
        return ...
    }
}
@end

@implementation controller
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSString *identifier = [_viewModel identifierWith:model];
    return [tableView dequeReusa....:identifier];
}
@end
複製程式碼

在設定資料來源方法中,我沒有進行cell為空的操作處理,因為需要在這之前,對tableView進行所有型別cell的註冊,個人也比較喜歡先註冊cell的方式,這樣子可以讓程式碼看起來更加直觀。


結尾:

回顧前面所講的,我的出發點可以理解為始終是一個---為controller制定瘦身計劃,同時使程式碼更加容易維護。 之前只是一直想著coding,把功能實現就好,而從來沒有想過將程式碼寫在哪裡會更加合理。 當然上面的方法並不是最佳?,自我總結,僅供參考,大神路過有意見,還望指點下。

本來還想通過嘗試tableView自動算高來鞏固複習tableView,然而發現這裡是一塊肥肉,考慮篇幅,還得重新開一篇筆記細細評味?。

參考

ReactiveCocoa and MVVM, an Introduction

相關文章