Section、row的个数不定的UITableView实现方式最佳实践
先解释下题目,比如下面这个设置页面,里面的内容会根据用户身份、权限等等看到不同的可设置内容:
- 比如左边有3个section;右边有4个
- 左边section[0]中有4个row;右边section[0]中有2个row
- 左边section[0] row[0]可点击;右边则不可点击
- 等等
我们先假设最简单的情况,造成左右两边差异的原因完全就是由于一个变量的值的不同导致,isMyHome,如果为YES,则最终看到左边的效果,如果为NO,则最终看到右边的效果。
问题来了,你会如何实现UITableView的各种delegate?
错误示范,一种很丑的实现方式
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if (self.isMyHome) {
return 3;
} else {
return 4;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (self.isMyHome) { // 有3个section
switch (section) {
case 0:
return 4;
break;
case 1:
return 1;
break;
case 2:
return 2;
break;
default:
break;
}
} else {
switch (section) {
case 0:
return 2;
break;
case 1:
return 1;
break;
case 2:
return 1;
break;
case 3:
return 1;
break;
default:
break;
}
}
return 0;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (self.isMyHome) {
if (indexPath.section == 2 && indexPath.row == 0) { // 有一个row稍微有点高
return 70;
} else {
return 40;
}
} else {
return 40;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = ni;
// ...真的不想写了快吐了,这才一个变量的区别
return cell;
}
以上的写法有两个大问题:
- 任何时候都要考虑isMyHome的值,写一堆if、else、switch。(一个row是否要显示,row的index是多少,row长什么样都是变量,都要考虑)
- 使用0,1,2,3等数字,没有含义,没有使用枚举,难以维护,增删一行都要改全部,还容易出bug。
这才一个变量啊,试想实际项目中,往往很多变量综合决定了哪个row显示、那个row不显示、row长什么样。想想下,如果有一天产品经理说,去掉“宝宝信息”这行,我觉得你就要疯了,要把index全部都减一,每个代理都要检查一遍是否对得上index。
所以,这两个问题,导致的后果:难实现、容易出bug、不易维护。
最佳实现
既然上面的实现这么不好,该怎么实现呢?其实就是逐一解决上面提到的两个问题。
对于1,其实造成问题的本质原因是:在任何时候,把一个row应该显示与否、以及长什么样子、row对应的index是什么, 这些问题都融合在一起了,将问题复杂程度一下提升了很多。对于2,都使用枚举就好了,避免
这里提出解决办法:
- 固定section的个数、每个section中row的个数,都取所有条件下,最多的那个值。(numberOfSectionsInTableView、numberOfRowsInSection只控制个数问题)
- heightForRowAtIndexPath中控制row的显示与否,如果不显示,只需要高度返回0就好了
- cellForRowAtIndexPath里,由于section、row的index都是固定不变的,只需要关心cell的内容就好了。
- 使用枚举
这样,“section、row的个数(index)”、“section、row是否显示”、“row长什么样”,这三个问题就分散到了4个delegate中:
- numberOfSectionsInTableView、numberOfRowsInSection只解决“section、row的个数(index)”的问题;
- heightForRowAtIndexPath只解决“section、row是否显示”的问题
- cellForRowAtIndexPath只解决“row长什么样”的问题
代码长这样:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 4;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
switch (section) {
case SectionOne:
return 4;
break;
case SectionTwo:
return 1;
break;
case SectionThree:
return 2;
break;
case SectionFour:
return 1;
break;
default:
break;
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) {
switch (indexPath.row) {
case SectionOneRowOne:
return 40
break;
case SectionOneRowTwo:
return self.isMyHome ? 40 : 0;
break;
case SectionOneRowThree:
return self.isMyHome ? 40 : 0;
break;
case SectionOneRowFour:
return 40
break;
default:
break;
}
} else if (indexPath.section == 1) {
return 40;
} else if (indexPath.section == 2) {
switch (indexPath.row) {
case SectionThreeRowOne:
return self.isMyHome ? 70 : 0;
break;
case SectionThreeRowTwo:
return 40;
break;
default:
break;
}
} else {
return self.isMyHome ? 0 : 40;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// 对于每个cell,配置内容就好了,index都是固定的
}
如果产品经理说,删除“宝宝信息”这行吧,那么我们只要把heightForRowAtIndexPath,相应高度改为0就好了,之后产品经理反悔还可以直接改回来,就算想完全删了,也很省事,不容易出bug。
总结
核心思想,其实就是,UITableView的几个delegate,分别只解决一个问题,不要把问题都揉在一起,让代码变得复杂
其他问题
假如要去掉一整个section怎么办,比如产品经理说,不要“我在小家的称呼”这行了。
当然,没问题,我们直接把heightForRowAtIndexPath里这行,改为0就好了嘛!
等等,虽然这行没了,但是前两个section之间的间隙,有可能会变大,因为只是设置了height为0而已,seciton还在嘛。这种情况怎么办?
不用担心,只要设置section的间隙,使用类似这种方式去设置,响应的改变section header的值,就好了~
其实,这种设置section的间隙的方式,也是我们经常使用的嘛,基本都不太用系统默认的间隙。
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return section == 0 ? 0 : 8;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
if (section == 0) {
return [UIView new];
}
UIView *sectionHeader = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 8)];
sectionHeader.backgroundColor = [UIColor clearColor];
return sectionHeader;
}