1. 程式人生 > >iOS開發之DataSource神奇魔法,優雅的寫法讓你輕鬆駕馭TableView

iOS開發之DataSource神奇魔法,優雅的寫法讓你輕鬆駕馭TableView

簡介

最近在重構之前寫的程式碼的時候,發現基本每個viewController裡面都有一段又臭又長的程式碼用於定義tableView的dataSourcedelegate,於是我在想,有沒有更優雅的方式來書寫dataSource,於是乎就產生了CBTableViewDataSource。

使用CBTableViewDataSource之前

// define a enum to split section

typedef NS_ENUM(NSInteger, SectionNameDefine) {
    SECTION_ONE,
    SECTION_TWO,
    SECTION_THREE,
    SECTION_FOUR,
    //...
COUNT_OF_STORE_SECTION }; // define identifier for section #define IDENTIFIER_ONE @"IDENTIFIER_ONE" #define IDENTIFIER_TWO @"IDENTIFIER_TWO" #define IDENTIFIER_THREE @"IDENTIFIER_THREE" #define IDENTIFIER_FOUR @"IDENTIFIER_FOUR" //... // register cell class for section [self.tableView registerClass:[OneCell class] forCellWithReuseIdentifier:IDENTIFIER_ONE]; [self
.tableView registerClass:[TwoCell class] forCellWithReuseIdentifier:IDENTIFIER_TWO]; [self.tableView registerClass:[ThreeCell class] forCellWithReuseIdentifier:IDENTIFIER_THREE]; [self.tableView registerClass:[FourCell class] forCellWithReuseIdentifier:IDENTIFIER_FOUR]; // implementation datasource protocol
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return COUNT_OF_STORE_SECTION; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return ((NSArray*)self.data[section]).count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { NSUInteger section = (NSUInteger) indexPath.section; NSUInteger index = (NSUInteger) indexPath.row; switch(section) { case SECTION_ONE: // to do something return cell; case SECTION_TWO: // to do something return cell; case SECTION_THREE: // to do something return cell; //... } return cell; } // ...

使用CBTableViewDataSource 之後

CBTableViewDataSource * dataSource = CBDataSource(self.tableView)
     .section()
     .cell([OneCell class])
     .data(self.viewModel.oneData)
     .adapter(^UITableViewCell *(OneCell * cell,NSDictionary * data,NSUInteger index){

         //bind data form data to cell

         return cell;
     })

     .section()
     .cell([TwoCell class])
     .data(self.viewModel.twoData)
     .adapter(^UITableViewCell *(TwoCell * cell,NSDictionary * data,NSUInteger index){

         //bind data form data to cell

         return cell;
     })
     // ...
     .make();

CBTableViewDataSource允許我們以函式式的方式定義dataSouce,邏輯順序和頁面的呈現順序一致。
每個section以section()開頭,在section()之後,可以對該section進行一些配置,要求每個section必須設定cell,data,和adapter。cell表示該section使用的cell類,data表示該section的資料,adapter用於將資料和cell繫結起來。同時還能配置section中cell的高度,或者設定自動計算高度。也可是設定section的標題,cell的點選事件等等。

CBTableViewDataSource主要解決了以下幾個問題:

  1. 避免了書寫各種亂七八糟的巨集定義,自動註冊cell類,自動設定identifier。
  2. 提供了一套完美解決不同高度cell的計算問題,提供自動計算cell高度的介面。
  3. 提供一套優雅的api,十分優雅並且有邏輯地書寫dataSource。

DEMO解讀

DEMO包括兩個頁面,First展示了複雜多section頁面時的用法,通過一個仿各種市面上流行的APP的首頁,體現了該框架書寫dataSource條理清晰,邏輯順序和頁面呈現的順序完全一致的優點。


IMG_0220.png
IMG_0221.png

second頁面通過一個Feed頁面,展示了autoHeight的用法。只要呼叫autoHeight函式,一句話解決cell高度計算問題。


IMG_0222.png

用法

Install

框架一共包括四個檔案

CBDataSourceMaker.h
CBDataSourceMaker.m

CBTableViewDataSource.h
CBTableViewDataSource.m

可以直接通過Pod下載使用

pod ....(還沒上傳)

或者直接將上述四個檔案複製到你的專案中即可使用。

Import

#import <CBTableViewDataSource/CBTableViewDataSource.h>

宣告

@property(nonatomic, retain) CBTableViewDataSource *  dataSource;

初始化

_dataSource = CBDataSource(self.tableView).section()
      .title(@"section one")
      .cell([TestCell class])
      .data(array)
      .adapter(^(TestCell * cell,NSDictionary * dic,NSUInteger index){
          cell.content.text = dic[@"content"];
          return cell;
      })
      .make()

!!!注意!!!
不能直接為dataSource賦值

//BAD
self.tableView.dataSource = CBDataSource(self.tableView)
    .section()
    .cell(...)
    .data(...)
    .adapter(...)
    .make()

因為UITableView的dataSource宣告的是weak,賦值完因為沒有任何強引用導致它的記憶體會被直接釋放。

API

CBDataSource(UITableView * tableView)

建立一個CBDataSourceMaker物件,用於建立CBTableViewDataSource,傳入一個需要繫結該dataSourcetableView物件

section()

用於分割多個section,每個section的開頭到要使用section()宣告一個section的開始

cell(Class cell)

傳入一個cell的class,如[UITableViewCell class]
表示當前section都使用這個cell,注意,cell不需要註冊,框架會自動註冊並繫結identifier

data(NSArray * data)

傳入一個數組,表示用於呈現在介面上的資料

adapter(^(id cell,id data,NSUInteger index))

介面卡,使用該方法將資料和cell繫結起來。
引數是一個block,該block會傳來一個cell物件,一個data物件,一個index。
可以直接在block上對引數型別進行強制轉換。
如:

adapter(^(GoodsCell * cell,GoodsModel * goods,NSUInterger index){
    cell.goods = goods;
    return cell;
})

headerView(UIView(*^**)())

設定tableHeaderView
引數是一個Block,要求返回一個UIView。

footerView(UIView*(^)())

設定tableFooterView
引數是一個Block,要求返回一個UIView。
常用於取消當頁面空白時,tableView呈現多餘的下劃線。
如:

footerView(^(){
    //返回一個空白View,這樣頁面沒內容時或者內容不足一頁,就不會出現多餘的線條。
    return [[UIView alloc]init];
})

height(CGFloat * height)

單獨為每個section設定一個固定的高度。
**有兩個特例:**

  • 當使用了autoHeight之後,該設定失效
  • 當在所有section之前設定height,將為所有section公共的height

autoHeight()

自動計算cell高度,用於cell高度不固定的情況。

注意:

  • 當cell的高度固定時,請不要使用autoHeight,因為autoHeight計算高度會消耗一定效能,儘管該框架已經對高度計算做了非常完美的快取處理,但是對於高效能的追求一定要做到精益求精。
  • 該設定只對autolayout有效。

一定要正確設定好約束:

  • 所有cell裡面的元件一定要放在cell.contentView裡面,不然會計算錯誤
  • 一定要有完整的約束。

確定一個約束是否完整有兩個原則

  1. 對於cell內部每個獨立的控制元件,都能確定位置和尺寸,比如左上角定在cell的左上角,然後設定高度寬度確定尺寸,或者設定右下角確定尺寸,前提是右下角相對的元件是能確定位置的。另外,UILabel和UIImageView,這種有內容的控制元件,只需要確定一個方向的尺寸,就會更具內容自動計算出另一個方向的尺寸,比如label知道寬度,和內容,就能算高度。
  2. 對於cell本身,必須能確定其尺寸。尺寸會通過約束其上下左右的控制元件來計算,這些所以約束其下和右的控制元件必須能確定位置和尺寸。值得說的是,這裡很容易遺漏掉底部的約束,因為cell就算沒有底部約束,也不會報錯,但是不能滿足計算出cell高度的必要條件。

event(^(NSUInteger index,id data))

引數要求一個Block,用於設定cell的點選事件,index表示點選了當前section的index位置,data表示當前點選位置的資料。

title(NSString* title)

用於設定每個section的標題。

make()

在設定完畢之後執行,表示已經設定完畢了。



文/cocbin(簡書作者)
原文連結:http://www.jianshu.com/p/efca5a075b75
著作權歸作者所有,轉載請聯絡作者獲得授權,並標註“簡書作者”。