[寫]UITableView之分割

UITableView是開發(fā)中經常用到的控制之一署照,但是每次實現(xiàn)起來都是大同小異。特別是UITableViewDelegateUITableViewDataSource 這兩個協(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高度自適應的文章扫尺,歡迎關注筋栋!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市正驻,隨后出現(xiàn)的幾起案子弊攘,更是在濱河造成了極大的恐慌抢腐,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件襟交,死亡現(xiàn)場離奇詭異迈倍,居然都是意外死亡,警方通過查閱死者的電腦和手機捣域,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門啼染,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人焕梅,你說我怎么就攤上這事迹鹅。” “怎么了贞言?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵斜棚,是天一觀的道長。 經常有香客問我该窗,道長弟蚀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任挪捕,我火速辦了婚禮粗梭,結果婚禮上争便,老公的妹妹穿的比我還像新娘级零。我一直安慰自己,他們只是感情好滞乙,可當我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布奏纪。 她就那樣靜靜地躺著,像睡著了一般斩启。 火紅的嫁衣襯著肌膚如雪序调。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天兔簇,我揣著相機與錄音发绢,去河邊找鬼。 笑死垄琐,一個胖子當著我的面吹牛边酒,可吹牛的內容都是我干的。 我是一名探鬼主播狸窘,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼墩朦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了翻擒?” 一聲冷哼從身側響起氓涣,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤牛哺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后劳吠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體引润,經...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年痒玩,在試婚紗的時候發(fā)現(xiàn)自己被綠了椰拒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡凰荚,死狀恐怖燃观,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情便瑟,我是刑警寧澤缆毁,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站到涂,受9級特大地震影響脊框,放射性物質發(fā)生泄漏。R本人自食惡果不足惜践啄,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一浇雹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧屿讽,春花似錦昭灵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至诵棵,卻和暖如春抠蚣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背履澳。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工嘶窄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人距贷。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓柄冲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親储耐。 傳聞我的和親對象是個殘疾皇子羊初,可洞房花燭夜當晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內容

  • 概述在iOS開發(fā)中UITableView可以說是使用最廣泛的控件,我們平時使用的軟件中到處都可以看到它的影子,類似...
    liudhkk閱讀 9,047評論 3 38
  • 序引 本系列文章將介紹iOS開發(fā)中的UITableView控件长赞,將會分成四篇文章完整的講述UITableView的...
    yetCode閱讀 2,265評論 3 40
  • 版權聲明:未經本人允許,禁止轉載. 1. TableView初始化 1.UITableView有兩種風格:UITa...
    蕭雪痕閱讀 2,908評論 2 10
  • 掌握 設置UITableView的dataSource晦攒、delegate UITableView多組數(shù)據(jù)和單組數(shù)據(jù)...
    JonesCxy閱讀 1,143評論 0 2
  • 因為業(yè)務需要會經常修改一些配置文件,而后臺比較簡陋得哆,沒有操作記錄脯颜。如果改錯了會比較麻煩。 修改前先自己手動備份贩据,然...
    觀星閱讀 946評論 0 0