如何優雅的對UITableView進行解耦

在本文以前筆者已經將相關代碼開源到 GitHub 上並添加了 CocoaPods 的支持,歡迎你們下載查看:STDTableView,下面進入正題:git

首先說一下本篇文章的背景,公司的一個項目中須要用到一個動態編輯的表單頁面,樣式以下圖所示: github

動態編輯表單
該表單頁面在項目中多處應用,且部分頁面須要支持定製,通過分析後大概可總結出如下的幾點要求:

  • cell 需支持鍵盤輸入,並須要對輸入作合法性驗證;
  • cell 須要能夠指定輸入的數據類型及提交時的數據類型;
  • cell 需支持各種彈出式選擇器(時間選擇、條目選擇、聯動選擇);
  • cell 需支持push到下個界面進行負責數據的選擇;
  • 表單須要支持在編輯時動態添加或者刪除條目;
  • 表單須要支持指定必填項與非必填項;
  • 表單須要支持指定不可編輯的條目;
  • 表單須要支持經過網絡加載回的數據進行初始化; ...

以往項目中的 tableView 結構已經沒法知足這種類型的需求了,特別是對 cell 內部事件處理,於是才萌生了對 UITableView 進行封裝的念頭,STDTableView 也是在這種狀況下誕生的,筆者基於這個庫完美的實現了上述的需求,並全線推行到公司的項目中。下面來聊一下 STDTableView 的實現思路:數組

能夠說 UITableView 是 iOS 開發中最經常使用的組件之一,關於它的接口及基本使用方法相信你們也都已經爛熟於胸,本文主要探討的是如何在業務爆炸的狀況下避免 viewController 變得臃腫不堪,以及如何更優雅簡潔的實現 UITableView 的相關調用。網絡

首先,先簡單的列舉一下平常開發中常常碰到的幾種狀況:優化

  1. 一個列表有多個 section,每一個 section 中的 cell 結構不一樣;
  2. 一個列表只有一個 section,可是 section 中存在多種 cell 結構;
  3. 須要在 viewController 中響應 cell 內部的點擊事件;

其中,針對上面的一、2兩點,若是不注意的話常常會寫出以下風格的代碼:ui

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = nil;

    if (indexPath.section == 0) {
        cell = xxx;
    } else if (indexPath.section == 1) {
        cell = xxx;
    } else {
        cell = xxx;
    }

    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.section == 0) {
        xxx
    } else if (indexPath.section == 1) {
        xxx
    } else {
        xxx
    }
}

複製代碼

相信你們在剛開始寫 tableView 的時候對上面的代碼都深有體會,單一類型的 cell 還好,可是若是後面須要增長不一樣的類型,或者根據不一樣的 row 跳轉到不一樣的頁面的話,這坨代碼會慢慢的龐大起來,最後可能本身看着都不想維護!spa

那麼有什麼方式來優化這個問題嗎?咱們不妨先從如下幾點着手:code

  1. 建立一個 dataAdapter 類,將 cell 的數據及基本配置如:cellReuseIdentifier、cellHeight、cellType 等保存起來作爲cell的數據源;
  2. 將 cell 的數據配置及選中回調封裝在自身,而後經過一個如 loadContent 以及 selectedEvent 的方法在 cellForRowAtIndexPath 和 didSelectRowAtIndexPath 中調用;
  3. 將 dataSource 從 viewController 中剝離;

因而咱們的代碼即可以變成以下的形式,viewController中也不須要每次都從新寫數據源方法:cdn

//獨立的數據源
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    STDTableViewItem *item = [self itemAtIndexPath:indexPath];
    
    STDTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:item.cellReuseIdentifier];
        
    [cell loadContent];
    
    return cell;
}

// viewController 中的 delegate 回調
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
	[(STDTableViewCell *)[tableView cellForRowAtIndexPath:indexPath] selectedEvent];
}
複製代碼

然而這個方式也存在一個問題,那就是 dataAdapter 的建立方式,通常咱們請求回來的數據都是直接解析成 model 數組,若是在 viewController 中直接進行顯式的轉換無疑也是極不友好的,這就須要咱們繼續作一層封裝了!blog

自此咱們已經能夠較好的解決前面提出的一、2兩點問題,針對問題 '3. 須要在 viewController 中響應 Cell 內部的點擊事件',筆者作法以下:

  1. 爲 cell 新增一個 delegate 方法 'tableViewCell:event:';
  2. 在 cell 初始化的時候設置 viewController 爲 delegate,並在 viewController 中實現相關的協議方法;
  3. 在 cell 的對應事件中執行相似 '[self.delegate tableViewCell:self event:@(STDEditEventInputFinish)]' 的操做告訴 viewController 發生了什麼;

根據這個思路進行封裝,咱們已經能夠較好的實現UITableView的解耦及調用簡化,接下去就是再作一些結構上的優化了!筆者根據這個思路封裝了一個庫 STDTableView 並附有相關的demo,支持CocoaPods安裝,歡迎你們下載查看,有什麼問題的話歡迎一塊兒討論或者提issue !

相關文章
相關標籤/搜索