iOS中通用的TableView和CollectionView DataSource和Cell

刚进公司不到一个月,接到一个需求,把一个项目的界面改一下。看了项目里的视图,耦合性强,没有复用,改起UI来很费劲。新的界面是个列表视图,就寻思怎么写出一个后面接手的人改起UI不那么费劲的tableview,顺便把项目里老的collectionView也进行了类比,偷偷进行了重构。毕竟UI总是改来改去的,而常用的tableview和collecitonview更是应该让其扩展性更好,复用性更强。

先从tableview讲起,tableview都很熟悉,一般就是由datasource,delegate,cell,cellitem这几部分组成。

打造一个通用性更强的DataSource

新建一个NSObject的子类,命名为CDZTableDataSource,并遵循<UITableViewDataSource>协议。对于datasource来说,必须实现的方法是-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

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

先看看第一个方法,返回每个section的row的数目,这个数字怎么来呢,就是section里的数据条数。如果不实现optional方法

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView的话,默认只有一个section。所以我们首先要确定的就是section的数目,所以对应的,我们应该有section的model。新建一个NSObject的子类,命名为CDZSectionObject。那么这个section应该有些什么属性呢?对于一个section对象来说,应该持有一个row对应的model的数组,这样就可以确定每个section的row的数量了。可能还有一些sectionheader是数据等等。

@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;   

那么现在就可以实现第一个方法了,为了方便我们还可以默认有一个firstsection,方便只有一个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;
}

轮到第二个方法了,配置cell的时候,我们需要知道cell对应的item而去配置cell,那么更抽象一点的话,也就是说datasource是建立了cellitem和cell之间的联系,也就是Model-View直接的联系。View本身做的工作也就是根据Model配置View的样子。所以这时候我们我们可以定义一个接口。

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

然后让我们的Cell遵守这个协议,也就是所有通用的Cell,需要在方法内实现item的解析。而其实对于cell也是一种view,这个接口可以让一些复用的view都遵守,比如headerview等等。对于cell我们还可以拓展这个协议。

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

这样,cell的高度也可以由cell内部自己决定,适合各种各样的cell,当然也可以让autolayout决定。但是有一些老项目里的cell里面没有autolayout,所以返回高度通用性可能更强些。

回到datasource,我们解决了cell的model问题,下一个是,cell的class怎么决定呢?其实对于datasouce来说,cell的类型,往往是由对应的model决定的,也就是model的类型,决定可以使用的cell的种类。那么我们在datasouce里,只要给出一个接口,让使用者建立这种联系,item class - cell class的联系,并在内部用一个类似哈希表的东西储存起来,我们便可以根据item找到cell的class。简单的话用一个NSMutableDictionary就可以了。

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

而这样以后要更换view的时候,只要新建一个cell并重新set一下对应的item的就可以了,换model也类似。

那么现在就可以根据index找到对应的section和对应的row所对应的item所对应的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];//没有对应关系的,取默认的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:)]) {//检查是否有实现setItem的方法
        [cell setItem:[self tableView:tableView itemForRowAtIndexPath:indexPath]];
    }
    return cell;
}

到此DataSource就完成了。

如何使用呢?

让自定义的cell遵守<CDZTableCell>协议,内部实现setItem方法去配置view。一些老的cell也可以简单的遵守协议并把以前配置视图的方法写到setItem方法里就可以了。协议比继承耦合性更低些,即加即用。

让tableview的datasouce指定为一个CDZTabeDataSource,并设置item对应的cell类型就可以。更换cell和item只要重新建立关系即可,达到了datasource的复用和拓展性。对于各种tableview,就不用重复写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其实也类似

区别在于collecitonview的cell要注册,所以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还可以共用一个sectionobject。

最后

所有源码和Demo
工作里,我们经常会因为赶需求而做一些复制粘贴。但也别忘了多思考如何复用,如何类比,虽然花掉现在一些时间,却会在日后的重构,修改,别人的修改上带来很多便利。

如果您觉得有帮助,不妨给个star鼓励一下,欢迎关注&交流
有任何问题欢迎评论私信或者提issue
QQ:757765420
Email:nemocdz@gmail.com
Github:Nemocdz
微博:@Nemocdz

谢谢观看

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容