UITableView如何开启极简模式

UITableView作为iOS开发的最常用的控件,相信对我们开发来说再熟悉不过了,但是越简单的越熟悉的东西,往往也可以看出代码的质量,项目的结构等问题。本文针对 UITableView中如何适应需求多变(新增删除、经常调换位置、高度变动等等)的通用解决方法如何避免同一套完全相同的UITableViewDelegate、UITableViewDataSource代码在不同UIViewController多次实现 两点进行展开讨论。不足之处还请指正。原文地址

一、 UITableView中如何适应需求多变(新增删除、经常调换位置、高度变动等等)的通用解决方法

拿我负责的楼盘详情来说:

因为产品会不时的参考运维及竞品产品,所以也会不时地对楼盘各个模块进行迭代调整,如果采用

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.row == 0) {
        //dosomething
    }
    else if (indexPath.row == 1) {
        //dosomething
    }
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.row == 0) {
        //didSelect
    }
    else if (indexPath.row == 1) {
        //didSelect
    }
}

进行代码兼容,对应的其他方法也得细心细心是的修正,想想都觉得可怕而又不保险,经过长期的磨合及快速适应产品需求而又让自己身心愉悦,必须得有一套完整而又通用的模式。

遵循一切皆对象的思维方式,我采取了 不同模块尽量使用独立的cell 处理,比如

这一块,尽量分两个cell实现,毕竟下一次需求 地址最新开盘 就分开了。

当然一个项目最好能有一个基类的UITableViewCell , 比如这样的:

@interface FDDBaseTableViewCell<ObjectType>: UITableViewCell
  
@property (nonatomic,weak) id<FDDBaseTableViewCellDelegate> fddDelegate;
@property (nonatomic,strong) ObjectType fddCellData;

+ (CGFloat)cellHeightWithCellData:(ObjectType)cellData;
- (void)setCellData:(ObjectType)fddCellData;    

@end

再者,随着 MVVM 模式的普及,项目中我也使用了一个中间的 cellModel 来控制 UITableViewUITableViewCell 的构建:

@interface FDDBaseCellModel : NSObject
  
@property (nonatomic, strong) id cellData;                      //cell的数据源
@property (nonatomic, assign) Class cellClass;                  //cell的Class
@property (nonatomic, weak)   id delegate;                      //cell的代理
@property (nonatomic, assign) CGFloat cellHeight;               //cell的高度,提前计算好
@property (nonatomic, strong) FDDBaseTableViewCell *staticCell; //兼容静态的cell

+ (instancetype)modelFromCellClass:(Class)cellClass cellHeight:(CGFloat)cellHeight cellData:(id)cellData;
- (instancetype)initWithCellClass:(Class)cellClass cellHeight:(CGFloat)cellHeight cellData:(id)cellData;

@end

一套通用构建 UITableView 的大致的思路如下:

对应的代码也就是这样:

- (void)disposeDataSources{
    NSArray *randomSources = @[@"Swift is now open source!",
                               @"We are excited by this new chapter in the story of Swift. After Apple unveiled the Swift programming language, it quickly became one of the fastest growing languages in history. Swift makes it easy to write software that is incredibly fast and safe by design. Now that Swift is open source, you can help make the best general purpose programming language available everywhere",
                               @"For students, learning Swift has been a great introduction to modern programming concepts and best practices. And because it is now open, their Swift skills will be able to be applied to an even broader range of platforms, from mobile devices to the desktop to the cloud.",
                               @"Welcome to the Swift community. Together we are working to build a better programming language for everyone.",
                               @"– The Swift Team"];
    for (int i=0; i<30; i++) {
        NSInteger randomIndex = arc4random() % 5;
        FDDBaseCellModel *cellModel = [FDDBaseCellModel modelFromCellClass:HDTableViewCell.class cellHeight:[HDTableViewCell cellHeightWithCellData:randomSources[randomIndex]] cellData:randomSources[randomIndex]];
        [self.dataArr addObject:cellModel];
    }
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return self.dataArr.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    FDDBaseCellModel *cellModel = self.dataArr[indexPath.row];
    FDDBaseTableViewCell *cell = [tableView cellForIndexPath:indexPath cellClass:cellModel.cellClass];
    [cell setCellData:cellModel.cellData delegate:self];
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    FDDBaseCellModel *cellModel = self.dataArr[indexPath.row];
    //dosomething
}

也就是无论有多少种不同类型、各种顺序排列的 UITableViewCell我们只需要关注数据源中的FDDBaseCellModel即可 ,而且 UITableViewDataSource 中的协议方法变得极为的简洁和通用。

二、如何避免同一套完全相同的UITableViewDelegate、UITableViewDataSource代码在不同UIViewController多次实现

有了前面的构想,我们会惊奇的发现,实现一个无论简单或者复杂的 UITableView 仅仅取决于包含 FDDBaseCellModel 的数据源!而所有包含 UITableViewUIViewControllerUITableViewDelegate、UITableViewDataSource 代码完全一致!

那么问题来了,怎么避免有如此的的重复代码在你优秀的项目中呢?

1、继承帮你忙:

在项目的 UIViewController 基类中,实现通用的 UITableViewDelegate、UITableViewDataSource 方法即可,毕竟数据源 self.dataArr 可以放在基类中,子类如果确实有通用方法无法处理的特殊情况,没有问题!各自子类重载对应的方法即可。Objective-CSwift 通用。

存在的问题:

1. 对非继承基类的 UIViewController 无力回天;
2. 对 UIView 中包含的 UITableView 无法做到兼容;
3. 当 UITableViewDelegate、UITableViewDataSource不是交给当前 UIViewController 时;
4. 等等等。。。

2、中间转换类(FDDTableViewConverter)实现:

2.1、通过响应模式来实现:

只需要判断 UITableView 的载体是否能响应对应的 UITableViewDelegate、UITableViewDataSource 方法,如果载体实现则使用载体本身的方法即可,这个其实和继承中重载的思路一致,但是少了一层继承依赖关系总是好的。Swift 不可用。

存在的问题:

1. 和继承方式一样,需要在当前类响应 UITableViewDelegate、UITableViewDataSource 方法;
2. 当 UITableViewDelegate、UITableViewDataSource 不是交给当前 UIViewController 时;
3. 因为载体不在遵循 UITableViewDelegate、UITableViewDataSource,写对应的方法是编译器无法给到代码联想补全功能,略尴尬。
4. 中间转换类需要实现大部分的 UITableViewDelegate、UITableViewDataSource 方法,尽量全面写完;
5. 响应模式中因为要在 转换类 中调用载体的方法、提供不定向的入参及接收返回值,使用 performSelector: 方法则不可行,在 Objective-C 中倒是可以使用 NSInvocation 实现,但是在 Swift 中 NSInvocation 已经被废弃,也就是只能兼容 Objective-C 代码。如果有其他方式兼容 swift 请立马告知我,谢谢!
6. 等等等。。。

2.2、通过注册模式来实现:

这种思维模式和AOP切片模式很像,哪里注册了 UITableViewDelegate、UITableViewDataSource 方法,哪里处理改方法,没有默认的统一走 中间转换类 的统一处理。实现方式是通过 NSMutableDictionary 来保存注册的 SELresultBlockresultBlock 传参放入一个数组中,个数和 SEL 中的入参保持一致,返回值是注册的载体返回给 中间转换类 的结果, 中间转换类 拿到这个值再给到 UITableViewDelegate、UITableViewDataSource 。好像有点转,看代码你肯定就清晰了:

FDDTableViewConverter 部分代码:

typedef id (^resultBlock)(NSArray *results);
@interface FDDTableViewConverter<TableViewCarrier>: NSObject <UITableViewDataSource, UITableViewDelegate>
  
//默认模式,使用注册方式处理tableView的一些协议
@property (nonatomic, assign) FDDTableViewConverterType converterType;
// 只有在选择 FDDTableViewConverter_Register 模式时,才会block回调
- (void)registerTableViewMethod:(SEL)selector handleParams:(resultBlock)block;

@end

UITableView 载体 ViewController 部分代码:

- (void)disposeTableViewConverter{
    _tableViewConverter = [[FDDTableViewConverter alloc] initWithTableViewCarrier:self daraSources:self.dataArr];
    
    UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
    tableView.delegate = _tableViewConverter;
    tableView.dataSource = _tableViewConverter;
    tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
    [self.view addSubview:tableView];
    
    __weak typeof(self) weakSelf = self;
    [_tableViewConverter registerTableViewMethod:@selector(tableView:cellForRowAtIndexPath:) handleParams:^id(NSArray *results) {
        UITableView *tableView = results[0];
        NSIndexPath *indexPath = results[1];
        FDDBaseCellModel *cellModel = weakSelf.dataArr[indexPath.row];
        FDDBaseTableViewCell *cell = [tableView cellForIndexPath:indexPath cellClass:cellModel.cellClass];
        [cell setCellData:cellModel.cellData delegate:weakSelf];
        return cell;
    }];
}

这种方式暂时看来是比较可取的方式了,无论从代码的整洁还是耦合度来说都是非常棒的模式了,而且它关注的是谁注册了对应的方法,你就在block拿到 中间转换类 的值来实现你特殊化的 UITableView , 再回传给 中间转换类 来替你实现。而且注册的 SEL 有代码联想补全功能,😁😁 Objective-CSwift 通用。

存在的问题:

1. 中间转换类需要实现大部分的 UITableViewDelegate、UITableViewDataSource 方法,尽量全面写完。
2. 等等等。。。

3、Swift通过Category实现:

SwiftObjective-CCategory 实现机制是不一样的,对于 Objective-C 来说,当前类和 Category 有相同方法时会优先执行 Category 中的方法,但是在 Swift 的世界里,同时存在同一个方法是不允许的,所以也就多了一个 override 关键字来优先使用当前类的方法。

实现方式也就是在 UITableView 载体的 Category 中实现通用的代码,然后使用 override 关键字来特殊化需要特殊处理的 方法即可。

比如这样:

FDDTableViewConverter.swift

extension FDDBaseViewController: FDDBaseTableViewCellDelegate {
    
    @objc(tableView:numberOfRowsInSection:) func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.dataArr.count
    }
    
    @objc(tableView:cellForRowAtIndexPath:) func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cellModel: FDDBaseCellModel = self.dataArr.object(at: indexPath.row) as! FDDBaseCellModel
        let cell: FDDBaseTableViewCell = tableView.cellForIndexPath(indexPath, cellClass: cellModel.cellClass)!
        cell.setCellData(cellModel.cellData, delegate: self)
        cell.setSeperatorAtIndexPath(indexPath, numberOfRowsInSection: self.dataArr.count)
        return cell
    }
    
    @objc(tableView:heightForRowAtIndexPath:) func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        let cellModel: FDDBaseCellModel = self.dataArr.object(at: indexPath.row) as! FDDBaseCellModel
        return CGFloat(cellModel.cellHeight)
    }
}

ViewController.swift

override internal func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 10
}

这种模式代码量非常少,并且实现起来很方便,Objective-C 不可用。

存在的问题:

1. 当在引入 FDDTableViewConverter.swift 后,因为Swift项目的特殊性(同模块中不需要导入该文件即可使用),这会导致以前的代码中,不是通用代码能实现的 UITableViewDelegate、UITableViewDataSource 方法前面都得加上 override 关键字;
2. 和继承有同样的毛病,不同的载体需要写上对应的category,貌似这块代码又是重复代码,苦逼;😅
3. 等等等。。。

三、小结:

上面的两个问题点是同事 @袁强 抛出给到我,但是解决问题的思路很多出至于 @凌代平 ,很庆幸有这么一次机会来码砖的机会。相信还会有其他更好的思路,如果你正好看到了请不吝赐教,🙏

代码整洁的道路很远,我相信只要需求理解到位,代码设计合理,我相信以后我们的实现 UITableView 时,只需要如下代码:

@implementation ViewController

- (void)dealloc{
    NSLog(@"%@ dealloc", NSStringFromClass(self.class));
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.title = @"ViewController";
    
    [self disposeDataSources];
    [self disposeTableViewConverter];
}

- (void)disposeDataSources{
    NSArray *randomSources = @[@"Swift is now open source!",
                               @"We are excited by this new chapter in the story of Swift. After Apple unveiled the Swift programming language, it quickly became one of the fastest growing languages in history. Swift makes it easy to write software that is incredibly fast and safe by design. Now that Swift is open source, you can help make the best general purpose programming language available everywhere",
                               @"For students, learning Swift has been a great introduction to modern programming concepts and best practices. And because it is now open, their Swift skills will be able to be applied to an even broader range of platforms, from mobile devices to the desktop to the cloud.",
                               @"Welcome to the Swift community. Together we are working to build a better programming language for everyone.",
                               @"– The Swift Team"];
    for (int i=0; i<30; i++) {
        NSInteger randomIndex = arc4random() % 5;
        FDDBaseCellModel *cellModel = [FDDBaseCellModel modelFromCellClass:HDTableViewCell.class cellHeight:[HDTableViewCell cellHeightWithCellData:randomSources[randomIndex]] cellData:randomSources[randomIndex]];
        [self.dataArr addObject:cellModel];
    }
}

- (void)disposeTableViewConverter{
    _tableViewConverter = [[FDDTableViewConverter alloc] initWithTableViewCarrier:self daraSources:self.dataArr];
    
    UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
    tableView.delegate = _tableViewConverter;
    tableView.dataSource = _tableViewConverter;
    tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
    [self.view addSubview:tableView];
}

@end

Objective-C源码地址

Swift源码地址

欢迎 star

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,019评论 4 62
  • ——《日 月》 9
    LuckySL阅读 194评论 0 0
  • 给一本经济学的书写书评向来不是一件容易的事情,特别是像薛兆丰老师写的《经济学通识》这种贴近中国现实的经济学入门读物...
    郭小鹏阅读 389评论 0 0
  • “天鸽”正面袭击珠海后,220kV南琴甲乙线倒塔7基,倾斜2基,24日晚上机具物资中心应现场指挥部的需求务必...
    机具物资中心阅读 244评论 0 0
  • 小时候看爱情片,觉得为了对方好甘愿放手即感人也伟大,后来长大,发觉这样的爱情根本就是独角戏,活该放手成前任。而亲情...
    郑经阿阅读 297评论 0 0