iOS開發之DataSource神奇魔法,優雅的寫法讓你輕鬆駕馭TableView
簡介
最近在重構之前寫的程式碼的時候,發現基本每個viewController裡面都有一段又臭又長的程式碼用於定義tableView的dataSource
和delegate
,於是我在想,有沒有更優雅的方式來書寫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主要解決了以下幾個問題:
- 避免了書寫各種亂七八糟的巨集定義,自動註冊cell類,自動設定identifier。
- 提供了一套完美解決不同高度cell的計算問題,提供自動計算cell高度的介面。
- 提供一套優雅的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
,傳入一個需要繫結該dataSource
的tableView
物件
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裡面,不然會計算錯誤
- 一定要有完整的約束。
確定一個約束是否完整有兩個原則
- 對於cell內部每個獨立的控制元件,都能確定位置和尺寸,比如左上角定在cell的左上角,然後設定高度寬度確定尺寸,或者設定右下角確定尺寸,前提是右下角相對的元件是能確定位置的。另外,UILabel和UIImageView,這種有內容的控制元件,只需要確定一個方向的尺寸,就會更具內容自動計算出另一個方向的尺寸,比如label知道寬度,和內容,就能算高度。
- 對於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
著作權歸作者所有,轉載請聯絡作者獲得授權,並標註“簡書作者”。