UITableView
是開發(fā)中經常用到的控制之一署照,但是每次實現(xiàn)起來都是大同小異。特別是UITableViewDelegate
和UITableViewDataSource
這兩個協(xié)議基本每次都是粘貼復制。再者加上實現(xiàn)加載數(shù)據(jù)俊啼,下拉刷新以及上拉加載更多等邏輯代碼剂陡,那么在控制器中的代碼量就會很大,對于后期的維護和后來者看代碼加大了難度咐蝇。其實可以把UITableView
模塊化涯鲁。
人狠話不多,直接來:
首先我們來看UITableViewDataSource
,常用的代理方法如下:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
前面兩個方法是代理中必須實現(xiàn)的有序,后面的是經常用到的抹腿。
分割數(shù)據(jù)源
考慮到代理中必須返回row
以及cell
。然后這里我們還要考慮到多個section
的情況旭寿,所以這里數(shù)據(jù)源需要是一個二維數(shù)組警绩,數(shù)組裝的是sectionModel
:
@interface SLTableViewSectionModel : NSObject
/// UITableDataSource 協(xié)議中的 titleForHeaderInSection 方法可能會用到
@property (nonatomic, copy) NSString *headerTitle;
/// UITableDataSource 協(xié)議中的 titleForFooterInSection 方法可能會用到
@property (nonatomic, copy) NSString *footerTitle;
/// 數(shù)據(jù)model數(shù)組
@property (nonatomic, strong) NSMutableArray *listModels;
- (instancetype)initWithModelArray:(NSMutableArray *)listModels;
@end
看到這個對象里面有一個listModels
,它是一個數(shù)組盅称,里面是每行cell
對應的model
數(shù)據(jù)肩祥,所以我們還需要一個基類BaseListModel
:
@interface SLBaseListModel : NSObject
//子類需要添加數(shù)據(jù)model屬性
///cell 高度
@property (nonatomic, assign) CGFloat cellHeight;
///初始化model 需要在子類重寫
- (instancetype)initWithData:(NSDictionary *)data;
@end
創(chuàng)建cell
數(shù)據(jù)model的時候僚匆,需要繼承上面的類,這樣方面后面通過model類型返回對應的cell class
搭幻。
數(shù)據(jù)類型被統(tǒng)一了咧擂,那么我們就可以創(chuàng)建一個UITableViewDataSource
基類:
///cell block 用于傳遞cell的按鈕點擊事件
typedef void (^SLTableViewCellBlock)(id cell, id item);
@protocol LslTableVDataSource <UITableViewDataSource>
@optional
//方便tableview delegate 調用以下方法 和 子類重寫
- (SLBaseListModel *)tableView:(UITableView *)tableView objectForRowAtIndexPath:(NSIndexPath *)indexPath;
- (Class)tableView:(UITableView *)tableView cellClassForObject:(SLBaseListModel *)model;
@end
@interface SLTableViewDataSouce : NSObject
<
LslTableVDataSource
>
///section 二維數(shù)組
@property (nonatomic, strong) NSMutableArray *sections;
///cell block
@property (nonatomic, copy) SLTableViewCellBlock CellBlock;
- (instancetype)initWithCellBlock:(SLTableViewCellBlock)cBlock;
- (void)clearAllModel;
///普通table 一個section 添加數(shù)據(jù)listModel
- (void)nomalAppendModel:(NSArray *)models;
///多個section 組合好了的數(shù)據(jù)model 直接賦值給sections
我們可以直接在基類里面實現(xiàn)UITableViewDataSource
的方法:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return self.sections ? [self.sections count] : 0;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section < [self.sections count]) {
SLTableViewSectionModel *sectionModel = self.sections[section];
return [sectionModel.listModels count];
}
return 0;
}
下面我們再看看返回cell
的實現(xiàn)方法:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
SLBaseListModel *listModel = [self tableView:tableView objectForRowAtIndexPath:indexPath];
Class class = [self tableView:tableView cellClassForObject:listModel];
NSString *className = [NSString stringWithUTF8String:class_getName(class)];
SLBaseTableViewCell *cell = (SLBaseTableViewCell *)[tableView dequeueReusableCellWithIdentifier:className];
if (!cell) {
cell = [[class alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:className];
}
//初始化cell數(shù)據(jù)
[cell initWithData:listModel];
if (self.CellBlock) {
self.CellBlock(cell,listModel);
}
return cell;
}
上面的實現(xiàn)主要是通過indexPath
找到對應的listModel
,然后通過listModel
找到對應的cell
檀蹋。就是方法:
- (SLBaseListModel *)tableView:(UITableView *)tableView objectForRowAtIndexPath:(NSIndexPath *)indexPath;
- (Class)tableView:(UITableView *)tableView cellClassForObject:(SLBaseListModel *)model;
可以看到這兩個方法是寫在protocol
里面的松申,并且繼承自UITableViewDataSource
,這樣是為什么呢?- (instancetype)initWithCellBlock:(SLTableViewCellBlock)cBlock
這個方法又是干什么的呢俯逾?后面會講贸桶,繼續(xù)往下看。
分割代理
代理里面最重要的也就是返回cell
高度:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath ;
首先我們首先需要創(chuàng)建個controller
:
@interface SLBaseTableViewController : UIViewController
<
UITableViewDelegate
>
/// UITableView
@property (nonatomic, strong) UITableView *baseTableView;
/// UITableViewStyle 默認 UITableViewStylePlain
@property (nonatomic, assign) UITableViewStyle tableViewStyle;
/// 分割線 默認 UITableViewCellSeparatorStyleSingleLine
@property (nonatomic, assign) UITableViewCellSeparatorStyle tableViewCellSeparatorStyle;
/// 背景色 默認白色
@property (nonatomic, strong) UIColor *tableViewBackgroundColor;
/// header 默認nil
@property (nonatomic, strong) UIView *headerView;
/// footer 默認nil
@property (nonatomic, strong) UIView *footerView;
///SLTableViewDataSouce
@property (nonatomic, strong) SLTableViewDataSouce *dataSource;
/// 加載更多view
@property (nonatomic, strong) SLTableLoadMoreView *loadMoreView;
/// 是否擁有下拉刷新 默認 NO
@property (nonatomic, assign) BOOL bNeedRefreshAction;
/// 是否擁有上拉加載更多 默認 NO
@property (nonatomic, assign) BOOL bNeedLoadMoreAction;
/// 是否刷新加載數(shù)據(jù) 默認 NO
@property (nonatomic, assign) BOOL bRefresh;
/// 設置table
- (void)createTableView;
/// 設置TableDatasource
- (void)initTableDatasource;
/// 初始化下拉刷新
- (void)initMJRefresh;
/// 初始化上拉加載
- (void)initLoadMore;
/// 刷新加載數(shù)據(jù)
- (void)beginRefresh;
/// 加載更多數(shù)據(jù)
- (void)loadMoreData;
這個類里面添加了tableView
桌肴,屬性以及刷新皇筛、加載更多方法。我們還是來一步一步的看實現(xiàn)吧:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
//獲取table datasource
id<LslTableVDataSource> dataSource = (id<LslTableVDataSource>)tableView.dataSource;
SLBaseListModel *listModel = [dataSource tableView:tableView objectForRowAtIndexPath:indexPath];
Class cls = [dataSource tableView:tableView cellClassForObject:listModel];
if (listModel.cellHeight == 0.0f) { // 沒有高度緩存
listModel.cellHeight = [cls cellHeight:listModel];
}
return listModel.cellHeight;
}
這個方法實現(xiàn)通過datasource
調用到方法坠七,找到對應的cell 返回高度水醋。這里把cell
的高度計算放到了cell
中,這樣可以減少控制器的代碼量以及優(yōu)化tableView
彪置。
cell
點擊:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
id<LslTableVDataSource> dataSource = (id<LslTableVDataSource>)tableView.dataSource;
SLBaseListModel *listModel = [dataSource tableView:tableView objectForRowAtIndexPath:indexPath];
[self clickedTableCell:listModel];
}
看看- (instancetype)initWithCellBlock:(SLTableViewCellBlock)cBlock
這個方法是干什么的:
__weak typeof(self) weakSelf = self;
SLTableViewCellBlock cellBlock = ^(DemoTableViewCell *cell, DemoListModel *model) {
//可以遍歷cell上的按鈕 將按鈕事件傳遞到控制器
[weakSelf forCellBtn:cell];
};
- (void)forCellBtn:(DemoTableViewCell *)cell {
for (UIView *view in cell.contentView.subviews ) {
if ([view isKindOfClass:[UIButton class]]) {
UIButton *btn = (UIButton *)view;
[btn addTarget:self action:@selector(clickedCellBtn:) forControlEvents:UIControlEventTouchUpInside];
}
}
}
- (void)clickedCellBtn:(UIButton *)btn {
//處理點擊事件
NSLog(@">>>clicked cell btn");
}
當然最重要的是:
self.baseTableView.dataSource = self.dataSource;
分割網絡加載
我們先搞一個基類:
@protocol ListRequestDelegate <NSObject>
///請求數(shù)據(jù)成功
- (void)requestDidSuccess:(NSArray *)listModels loadMore:(BOOL)bHaveMoreData;
///請求失敗
- (void)requestDidFail:(NSDictionary *)error;
@end
@interface SLBaseTableListRequest : SLBaseRequest
///接口路徑
@property (nonatomic, copy) NSString *dataPath;
///頁數(shù) 默認1
@property (nonatomic, assign) NSUInteger currentPage;
///每頁條數(shù) 默認10
@property (nonatomic, assign) NSUInteger rows;
///請求代理
@property (nonatomic, weak) id<ListRequestDelegate> delegate;
///加載數(shù)據(jù)
- (void)loadData:(BOOL)bRefresh;
@end
.m主要實現(xiàn)
- (void)loadData:(BOOL)bRefresh {
[super loadData:bRefresh];
if (bRefresh) {
self.currentPage = 1;
} else {
self.currentPage ++;
}
[self loadData];
}
- (void)loadData {
__weak typeof (self) weakSelf = self;
[AFNetHttpManager postWithUrl:[self requestUrl] params:[self requestAllArgument] success:^(id result) {
[weakSelf dicToModel:result[@"tngou"]];
} fail:^(NSDictionary *errorInfo) {
if (self.delegate && [self.delegate respondsToSelector:@selector(requestDidFail:)]) {
[self.delegate requestDidFail:errorInfo];
}
}];
}
- (void)dicToModel:(NSArray *)list {
NSMutableArray *listModel = [[NSMutableArray alloc] init];
for (NSDictionary *dic in list) {
DemoListModel *model = [[DemoListModel alloc] initWithData:dic];
[listModel addObject:model];
}
BOOL bMore = NO;
if ([list count] == self.rows) {
bMore = YES;
}
if (self.delegate && [self.delegate respondsToSelector:@selector(requestDidSuccess:loadMore:)]) {
[self.delegate requestDidSuccess:listModel loadMore:bMore];
}
}
這樣在控制器中只需要實現(xiàn)代理:
- (void)requestDidSuccess:(NSArray *)listModels loadMore:(BOOL)bHaveMoreData
- (void)requestDidFail:(NSString *)error
就OK了拄踪。
吃個??:
- (void)beginRefresh {
self.bRefresh = YES;
[self.loadMoreView stopLoadMore];
[self.listRequst loadData:self.bRefresh];
}
- (void)loadMoreData {
self.bRefresh = NO;
[self.loadMoreView startLoadMore];
[self.baseTableView.mj_header endRefreshing];
[self.listRequst loadData:self.bRefresh];
}
- (void)requestDidSuccess:(NSArray *)listModels loadMore:(BOOL)bHaveMoreData {
//請求 數(shù)據(jù)成功
//如果是刷新,清除之前的數(shù)據(jù)
if (self.bRefresh) {
[self.dataSource clearAllModel];
[self.baseTableView.mj_header endRefreshing];
if (bHaveMoreData && self.bNeedLoadMoreAction) {
//添加加載更多
[self initLoadMore];
} else {
self.baseTableView.tableFooterView = [UIView new];
}
} else {
//加載更多
[self.loadMoreView stopLoadMore];
}
[self.dataSource nomalAppendModel:listModels];
[self.baseTableView reloadData];
}
- (void)requestDidFail:(NSString *)error {
//請求 數(shù)據(jù)失敗
[self.baseTableView.mj_header endRefreshing];
[self.loadMoreView stopLoadMore];
}
實現(xiàn)了table的下拉刷新和加載更多數(shù)據(jù)拳魁,看著是不是比沒分割之前的代碼清爽得一逼惶桐。雖然整體的代碼量并沒減少多少,但是分割之后看著鼻子是鼻子潘懊,眼睛是眼睛姚糊,這樣才是一個正常的人。
(其實還有很多tableView
功能可以進行分割授舟,比如:cell
的編輯救恨,也不復雜!有興趣的老鐵可以試試)
代碼不分多少岂却,只分思想忿薇。
詳情見 Demo:SLTableView
如果你覺得對你有用,請star躏哩!
以上僅限個人愚見署浩,歡迎吐槽!
參考:@bestswifter 如何寫好一個UITableView
后續(xù)有時間會寫tableView
高度自適應的文章扫尺,歡迎關注筋栋!