iOS中通用的TableView和CollectionView DataSource和Cell

剛進(jìn)公司不到一個(gè)月,接到一個(gè)需求孙蒙,把一個(gè)項(xiàng)目的界面改一下项棠。看了項(xiàng)目里的視圖挎峦,耦合性強(qiáng)香追,沒有復(fù)用,改起UI來很費(fèi)勁坦胶。新的界面是個(gè)列表視圖透典,就尋思怎么寫出一個(gè)后面接手的人改起UI不那么費(fèi)勁的tableview晴楔,順便把項(xiàng)目里老的collectionView也進(jìn)行了類比,偷偷進(jìn)行了重構(gòu)峭咒。畢竟UI總是改來改去的税弃,而常用的tableview和collecitonview更是應(yīng)該讓其擴(kuò)展性更好,復(fù)用性更強(qiáng)讹语。

先從tableview講起钙皮,tableview都很熟悉蜂科,一般就是由datasource顽决,delegate,cell导匣,cellitem這幾部分組成才菠。

打造一個(gè)通用性更強(qiáng)的DataSource

新建一個(gè)NSObject的子類,命名為CDZTableDataSource贡定,并遵循<UITableViewDataSource>協(xié)議赋访。對(duì)于datasource來說,必須實(shí)現(xiàn)的方法是-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

先看看第一個(gè)方法缓待,返回每個(gè)section的row的數(shù)目蚓耽,這個(gè)數(shù)字怎么來呢,就是section里的數(shù)據(jù)條數(shù)旋炒。如果不實(shí)現(xiàn)optional方法

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView的話步悠,默認(rèn)只有一個(gè)section。所以我們首先要確定的就是section的數(shù)目瘫镇,所以對(duì)應(yīng)的鼎兽,我們應(yīng)該有section的model。新建一個(gè)NSObject的子類铣除,命名為CDZSectionObject谚咬。那么這個(gè)section應(yīng)該有些什么屬性呢?對(duì)于一個(gè)section對(duì)象來說尚粘,應(yīng)該持有一個(gè)row對(duì)應(yīng)的model的數(shù)組择卦,這樣就可以確定每個(gè)section的row的數(shù)量了±杉蓿可能還有一些sectionheader是數(shù)據(jù)等等互捌。

@property (nonatomic, copy) NSString *headerTitle;
@property (nonatomic, copy) NSString *headerReuseIdentifer;
@property (nonatomic, strong) NSMutableArray *itemsArray;

回到datasouce,那么datasouce就可以加上這些屬性行剂。

@property (nonatomic, strong) NSMutableArray<CDZSectionObject *> *sectionsArray;
@property (nonatomic, strong) CDZSectionObject *firstSection;   

那么現(xiàn)在就可以實(shí)現(xiàn)第一個(gè)方法了秕噪,為了方便我們還可以默認(rèn)有一個(gè)firstsection,方便只有一個(gè)section的情況使用厚宰。

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    if (self.sectionsArray.count > section) {
        return self.sectionsArray[section].itemsArray.count;
    }
    return 0;
}


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    return self.sectionsArray ? self.sectionsArray.count : 0;
}

- (CDZSectionObject *)firstSection{
    if (!_firstSection) {
        _firstSection = [[CDZSectionObject alloc]init];
        [self.sectionsArray addObject:_firstSection];
    }
    return _firstSection;
}

輪到第二個(gè)方法了腌巾,配置cell的時(shí)候遂填,我們需要知道cell對(duì)應(yīng)的item而去配置cell,那么更抽象一點(diǎn)的話澈蝙,也就是說datasource是建立了cellitem和cell之間的聯(lián)系吓坚,也就是Model-View直接的聯(lián)系。View本身做的工作也就是根據(jù)Model配置View的樣子灯荧。所以這時(shí)候我們我們可以定義一個(gè)接口礁击。

@protocol CDZCustomView
@required
- (void)setItem:(id)item;
@end

然后讓我們的Cell遵守這個(gè)協(xié)議,也就是所有通用的Cell逗载,需要在方法內(nèi)實(shí)現(xiàn)item的解析哆窿。而其實(shí)對(duì)于cell也是一種view,這個(gè)接口可以讓一些復(fù)用的view都遵守厉斟,比如headerview等等挚躯。對(duì)于cell我們還可以拓展這個(gè)協(xié)議。

@protocol CDZTableCell <CDZCustomView>
@required
+ (CGFloat)tableView:(UITableView *)tableView rowHeightForItem:(id)item;
@end

這樣擦秽,cell的高度也可以由cell內(nèi)部自己決定码荔,適合各種各樣的cell,當(dāng)然也可以讓autolayout決定感挥。但是有一些老項(xiàng)目里的cell里面沒有autolayout缩搅,所以返回高度通用性可能更強(qiáng)些。

回到datasource触幼,我們解決了cell的model問題硼瓣,下一個(gè)是,cell的class怎么決定呢域蜗?其實(shí)對(duì)于datasouce來說巨双,cell的類型,往往是由對(duì)應(yīng)的model決定的霉祸,也就是model的類型筑累,決定可以使用的cell的種類。那么我們?cè)赿atasouce里丝蹭,只要給出一個(gè)接口慢宗,讓使用者建立這種聯(lián)系,item class - cell class的聯(lián)系奔穿,并在內(nèi)部用一個(gè)類似哈希表的東西儲(chǔ)存起來镜沽,我們便可以根據(jù)item找到cell的class。簡單的話用一個(gè)NSMutableDictionary就可以了贱田。

- (void)setCellClass:(Class)cellClass forItemClass:(Class)itemClass{
    if (!itemClass || !cellClass) {
        return;
    }
    [self.cellDic setObject:cellClass forKey:NSStringFromClass(itemClass)];
}

而這樣以后要更換view的時(shí)候缅茉,只要新建一個(gè)cell并重新set一下對(duì)應(yīng)的item的就可以了,換model也類似男摧。

那么現(xiàn)在就可以根據(jù)index找到對(duì)應(yīng)的section和對(duì)應(yīng)的row所對(duì)應(yīng)的item所對(duì)應(yīng)的cellclass了蔬墩。

- (id)tableView:(UITableView *)tableView itemForRowAtIndexPath:(NSIndexPath *)indexPath{
    if (self.sectionsArray.count > indexPath.section) {
        if (self.sectionsArray[indexPath.section].itemsArray.count > indexPath.row) {
            return self.sectionsArray[indexPath.section].itemsArray[indexPath.row];
        }
    }
    return nil;
}

- (Class)tableView:(UITableView *)tableView cellClassForRowAtIndexPath:(NSIndexPath *)indexPath{
    id item = [self tableView:tableView itemForRowAtIndexPath:indexPath];
    Class cellClass = [self.cellDic objectForKey:NSStringFromClass([item class])];
    if (!cellClass) {
        cellClass = [UITableViewCell class];//沒有對(duì)應(yīng)關(guān)系的译打,取默認(rèn)的UITalbeViewCell
    }
    return cellClass;
}

那么cellforrow方法就出來了

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    Class cellClass = [self tableView:tableView cellClassForRowAtIndexPath:indexPath];
    UITableViewCell<CDZTableCell> *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass(cellClass)];
    if (!cell) {
        cell = [[cellClass alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:NSStringFromClass(cellClass)];
    }
    if ([cell respondsToSelector:@selector(setItem:)]) {//檢查是否有實(shí)現(xiàn)setItem的方法
        [cell setItem:[self tableView:tableView itemForRowAtIndexPath:indexPath]];
    }
    return cell;
}

到此DataSource就完成了。

如何使用呢拇颅?

讓自定義的cell遵守<CDZTableCell>協(xié)議奏司,內(nèi)部實(shí)現(xiàn)setItem方法去配置view。一些老的cell也可以簡單的遵守協(xié)議并把以前配置視圖的方法寫到setItem方法里就可以了樟插。協(xié)議比繼承耦合性更低些韵洋,即加即用。

讓tableview的datasouce指定為一個(gè)CDZTabeDataSource黄锤,并設(shè)置item對(duì)應(yīng)的cell類型就可以搪缨。更換cell和item只要重新建立關(guān)系即可,達(dá)到了datasource的復(fù)用和拓展性猜扮。對(duì)于各種tableview勉吻,就不用重復(fù)寫datasource的代碼了监婶。例子如Demo

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    Class cellClass = [self.tableDataSource tableView:tableView cellClassForRowAtIndexPath:indexPath];
    return (cellClass == [UITableViewCell class]) ? 44.f : [cellClass tableView:tableView rowHeightForItem:[self.tableDataSource tableView:tableView itemForRowAtIndexPath:indexPath]];
}


- (UITableView *)tableView{
    if (!_tableView) {
        _tableView = [[UITableView alloc]initWithFrame:self.view.frame style:UITableViewStylePlain];
        _tableView.delegate = self;
        _tableView.dataSource = self.tableDataSource;
    }
    return _tableView;
}

- (CDZTableDataSource *)tableDataSource{
    if (!_tableDataSource) {
        _tableDataSource = [[CDZTableDataSource alloc]init];
        [_tableDataSource setCellClass:[CDZTableViewCell class] forItemClass:[CDZCellItem class]];
        _tableDataSource.firstSection.itemsArray = [[self mockItems] copy];
    }
    return _tableDataSource;
}

CollecitonViewDatasource其實(shí)也類似

區(qū)別在于collecitonview的cell要注冊(cè)旅赢,所以cellforrow方法有所不同

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    Class cellClass = [self collectionView:collectionView cellClassForRowAtIndexPath:indexPath];
    UICollectionViewCell<CDZCollectionCell> *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass(cellClass) forIndexPath:indexPath];
    if ([cell respondsToSelector:@selector(setItem:)]) {
        [cell setItem:[self collectionView:collectionView itemForRowAtIndexPath:indexPath]];
    }
    return cell;
}

其它基本類似,且collectionview和tableview還可以共用一個(gè)sectionobject惑惶。

最后

所有源碼和Demo
工作里煮盼,我們經(jīng)常會(huì)因?yàn)橼s需求而做一些復(fù)制粘貼。但也別忘了多思考如何復(fù)用带污,如何類比僵控,雖然花掉現(xiàn)在一些時(shí)間,卻會(huì)在日后的重構(gòu)鱼冀,修改报破,別人的修改上帶來很多便利。

如果您覺得有幫助,不妨給個(gè)star鼓勵(lì)一下,歡迎關(guān)注&交流
有任何問題歡迎評(píng)論私信或者提issue
QQ:757765420
Email:nemocdz@gmail.com
Github:Nemocdz
微博:@Nemocdz

謝謝觀看

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末千绪,一起剝皮案震驚了整個(gè)濱河市充易,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌荸型,老刑警劉巖盹靴,帶你破解...
    沈念sama閱讀 222,378評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瑞妇,居然都是意外死亡稿静,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門辕狰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來改备,“玉大人,你說我怎么就攤上這事蔓倍⌒” “怎么了润脸?”我有些...
    開封第一講書人閱讀 168,983評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長他去。 經(jīng)常有香客問我毙驯,道長,這世上最難降的妖魔是什么灾测? 我笑而不...
    開封第一講書人閱讀 59,938評(píng)論 1 299
  • 正文 為了忘掉前任爆价,我火速辦了婚禮,結(jié)果婚禮上媳搪,老公的妹妹穿的比我還像新娘铭段。我一直安慰自己,他們只是感情好秦爆,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評(píng)論 6 398
  • 文/花漫 我一把揭開白布序愚。 她就那樣靜靜地躺著,像睡著了一般等限。 火紅的嫁衣襯著肌膚如雪爸吮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,549評(píng)論 1 312
  • 那天望门,我揣著相機(jī)與錄音形娇,去河邊找鬼。 笑死筹误,一個(gè)胖子當(dāng)著我的面吹牛桐早,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播厨剪,決...
    沈念sama閱讀 41,063評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼哄酝,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了祷膳?” 一聲冷哼從身側(cè)響起陶衅,我...
    開封第一講書人閱讀 39,991評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎钾唬,沒想到半個(gè)月后万哪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,522評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了窘面。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,742評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡的止,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出着撩,到底是詐尸還是另有隱情诅福,我是刑警寧澤匾委,帶...
    沈念sama閱讀 36,413評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站氓润,受9級(jí)特大地震影響赂乐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜咖气,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評(píng)論 3 335
  • 文/蒙蒙 一挨措、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧崩溪,春花似錦浅役、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至乳幸,卻和暖如春瞪讼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背反惕。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評(píng)論 1 274
  • 我被黑心中介騙來泰國打工尝艘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留演侯,地道東北人姿染。 一個(gè)月前我還...
    沈念sama閱讀 49,159評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像秒际,于是被迫代替她去往敵國和親悬赏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容