可能以前的表述不是特别明确,个人方案不是静态页面的通用实现。个人方案针对的是相似一些设置界面之类的简单的静态的tableView。看到有不少人认为这样的方案感受实现起来会变麻烦,这个可能就是思考问题的侧重点不一样。我思考的侧重点是后期的维护修改、应对频繁的需求变动 欢迎交流~~ios
静态的tableView
相似设置界面、我的主页等等几乎是每一个APP都会涉及到的一个模块。我相信你们都有一些本身的套路来如何处理这类界面。写这篇文章的目的是抛砖引玉想要和你们来交流交流。git
从具体的写法来切入,如下是我能想到的一些写法。程序员
啥都不封装github
if (indexPath.section == 0) {
if (indexPath.row == 0) {
}else if (indexPath.row == 1) {
}
}else if (indexPath.section == 1) {
if (indexPath.row == 0) {
}else if (indexPath.row == 1) {
}
}
复制代码
各类嵌套if
判断indexPath.section
indexPath.row
拿到对应的cell显示或者跳转。这种方式可读性差,很差扩展应该没人这么写了吧,可能你刚学iOS开发的时候这么写过。swift
用一条枚举来对应的一条cell。 数据源用枚举数组,或者也能够用带有枚举属性的对象数组。数组
self.dataArray = @[@[@(settingTypeAccount)],
@[@(settingTypeMessage),@(settingTypePrivacy)],
@[@(settingTypeHelp),@(settingTypeAboutUs)]];
复制代码
代理方法里能够拿到枚举直接用switch判断xcode
switch (type) {
case settingTypeHelp:
break;
.
.
.
default:
break;
}
复制代码
用switch来判断具体具体的cell,首先可读性相较于if判断高了很多,而且在增长删除cell的状况下,xcode会有提示来帮助不至于漏掉一些地方。这种方式会有比较多的重复代码,并且在添加、删除、调整cell的时候不够高效。安全
首先storyboard
方式相较于纯代码,不用跑起来就能看见界面,相对比较直观。并且在开发速度方面也有不小的优点。可是我从自身开发过程当中的状况看来,这种方式在需求频繁变动的状况下仍是比较蛋疼的。bash
没有什么封装是加一层中间层解决不了的,若是有那么再加一层 -- 鲁迅ui
😁开个玩笑,来看看加一层怎么样操做。首先我认为要有一个概念,封装在必定程度上是不会减小代码量的。该写的代码你仍是要写的,只是合理的结构可让代码可读性更好,可扩展性也更好。
@interface tableModel : NSObject
- (void)addASection:(tableSectionModel *)section;
@property (nonatomic,strong) NSMutableArray <tableSectionModel *> *sections;
.
.
.
@end
@interface tableSectionModel : NSObject
- (void)addARow:(tableRowModel *)row;
@property (nonatomic,strong) NSMutableArray <tableRowModel *> *rows;
.
.
.
@end
@interface tableRowModel : NSObject
@property (nonatomic,assign) NSInteger rowHeight;
.
.
.
@end
复制代码
咱们一开始就已近知道了table是如何展现的,包括cell的显示顺序,cell的显示样式、行高等等。那么咱们能够把可以描述一个cell的全部的数据都抽象成一个rowModel的数据,而后把能描述每一个section的的全部数据抽象成一个sectionModel的数据。那么咱们只须要生成对应的sectionModel的数组就能够来描述一个table了,而后咱们在数据源里解析model里面的数据完成显示。这种方式已经相对比较合理了,可是内部仍是有比较大的封装余地。
我看过挺多的相似三行代码实现设置界面
的方案,基本都是上面第四种方法的进一步封装,内部实现了几种常见的cell样式,用一个枚举来对应具体的样式,而后给每一个每一个rowModel添加一个cell样式的属性。这样一来经过简单的设置rowModel的cell样式就能拿到具体的cell。固然这样的方式已经可以应对大部分的状况,而且写起界面来也是很爽了。可是我仍是有几个地方不是太满意,须要去尝试解决这些不满意。
我但愿添加section和row的时候这部分的代码是一个总体,单纯- (void)addASection:(tableSectionModel *)section;
这样的写法在我看来还不够总体,由于你没办法保证这部分的代码必定是写在了一个地方。思来想去,我想到了Masonry
的写法。
[self.view mas_makeConstraints:^(MASConstraintMaker *make) {
// do something
}];
复制代码
这种写法解决了我不满意的地方,因此最后我但愿的写法是
[self.tableView zhn_addSection:^(ZHNStaticTableSection *section) {
[section zhn_addRow:^(ZHNStaticTableRow *row) {
}];
.
.
.
}];
复制代码
dataSource
delegate
重复代码的问题咱们清楚dataSource
和delegate
是一对一的,因此代理方法和数据源方法确定是只能写在一个地方。你可能会说那么咱们再加一层Manger来管理sectionModel的数组,而后把tableView
的数据源和代理设置为manager,而后在manager内部实现dataSource
和delegate
解析sectionModel的数组展现界面。这样一来咱们只须要配置sectionModel就能够了。可是这样作那么万一咱们在控制器上想要监听tableView
的滑动呢?思来想去,我最后尝试用消息转发 + 断言
的方式来尝试解决这个问题。
代理是一对一的,通知是一对多的。
刚开始学iOS的时候,咱们确定都听过这样一句话,来描述代理和通知的不一样。可是其实经过消息转发,咱们也是能够来实现代理的一对多的。
若是对消息转发没啥概念的能够看看这篇博客。简单理解就是当调用方法的时候,系统经过isa指针层层查找方法列表,找不到方法的时候,在报找不到方法以前,系统还额外提供了几个方法提供给咱们去实现这个方法。
代理一对多的主要实现的逻辑:
1.提供一个delegate容器,存放代理。
2.- (BOOL)respondsToSelector:(SEL)aSelector
判断代理容器里的代理若是实现了代理方法,这个方法须要返回YES。若是返回NO,系统就断定没有实现代理方法,那么就不会调用方法,那么也就不会有后面的一系列的流程了。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
返回方法的签名。不返回签名后面的消息转发方法也不会调用。- (void)forwardInvocation:(NSInvocation *)anInvocation
方法里遍历delegate容器,转发方法。断言 (assertion) 在 Cocoa 开发里通常用来在检查输入参数是否知足必定条件,并对其进行“论断”。这是一个编码世界中的哲学问题,咱们代码的使用者 (有多是别的程序员,也有多是将来的本身) 很难作到在不知道实现细节的状况下去对本身的输入进行限制。大多数时候编译器能够帮助咱们进行输入类型的检查,可是若是代码须要在特定的输入条件下才能正确运行的话,这种更细致的条件就难以控制了。在超过边界条件的输入的状况下,咱们的代码可能没法正确工做,这就须要咱们在代码实现中进行一些额外工做。
上面这段介绍是从喵神一篇断言tips里的摘抄。在不少的第三方库中你确定也见过相似好比AFNetworking
中随便一搜NSAssert(NO, @"State method should never be called in the actual dummy class");
相似的断言很是常见。简单理解当咱们输入一个不合法参数的状况的时候,程序就直接崩溃了,而且打印了断言里的描述。那么咱们一眼就能知道咱们的输入出问题了,而且问题出在哪里。
这里咱们为何用断言,因为我内部实现了某些数据源和代理。那么咱们确定不但愿外部再实现这些实现过的方法。那么咱们确定须要作一些约束,这里用断言显然是最合适的。若是外部实现了实现过的方法,直接崩溃而且打印提示信息。
相似三行代码实现设置界面
的方案内部定义几种样式的方案,若是咱们想要把咱们已经写好的界面切换到这种方案下,代价相对仍是比较大的。咱们项目中确定也实现了一些cell,我但愿我以前的cell能无缝的接入进去。针对这种状况我在rowModel里添加了一个cellClass属性来指定cell,和一个displayCellHandle
block
来设置cell的一些样式。
[self.tableView zhn_initializeEnvironmentWithDefaultRowHeight:44
defaultCellClass:[NormalSettingTableViewCell class]
defaultSectionHeader:nil
defaultHeaderHeight:20
defaultSectionFooter:nil
defaultFooterHeight:0
originalDelegate:self
originalDatasource:self];
[self.tableView zhn_addSection:^(ZHNStaticTableSection *section) {
[section zhn_addRow:^(ZHNStaticTableRow *row) {
row.displayCellHandle = ^(UITableView *tableView, UITableViewCell *cell, NSIndexPath *indexPath) {
cell.textLabel.text = @"帐号与安全";
};
row.selectCellHandle = ^(UITableView *tableView, NSIndexPath *indexPath) {
NSLog(@"帐户与安全");
};
}];
}];
[self.tableView zhn_addSection:^(ZHNStaticTableSection *section) {
[section zhn_addRow:^(ZHNStaticTableRow *row) {
row.displayCellHandle = ^(UITableView *tableView, UITableViewCell *cell, NSIndexPath *indexPath) {
cell.textLabel.text = @"新消息通知";
};
}];
[section zhn_addRow:^(ZHNStaticTableRow *row) {
row.displayCellHandle = ^(UITableView *tableView, UITableViewCell *cell, NSIndexPath *indexPath) {
cell.textLabel.text = @"隐私";
};
}];
[section zhn_addRow:^(ZHNStaticTableRow *row) {
row.displayCellHandle = ^(UITableView *tableView, UITableViewCell *cell, NSIndexPath *indexPath) {
cell.textLabel.text = @"通用";
};
}];
}];
复制代码
代码在这里 github.com/zhnnnnn/ZHN…
这是个人方案,还没来得及在实际的项目中使用过。抛砖引玉,但愿你们可以不吝赐教。我也很想知道大型知名项目里你们都是怎么写这部分代码的。