继续上一篇,本周的重构重点是UI部分代码的优化。
1. AutoLayout及Masonry
AutoLayout是苹果在IOS6中推出的一种新的UI构建方式,旨在解决不同屏幕分辨率之间的适配问题。相信大多数人可能跟我一样,对这种方式是又爱又恨,因为AutoLayout中的确存在很多坑。不过随着iOS设备尺寸越来越多,还是值得去学习掌握AudoLayout的。
本次重构中在UITableViewCell中使用AutoLayout上遇到了一个坑,正常情况下在cell中使用AutoLayout需设置约束上下左右都为-8才能铺满整个Cell。但发现在iOS6中没有问题,但在iOS7以上,左右约束需设置为-15才能铺满。我的解决方案是在cell.contentView上再添加一层父View,针对不同的系统做了一个适配。但问题的根本原因目前还没有找到,有待后续观察。
另外,对于手写约束来说,使用苹果原生的API可能会很痛苦,因为约束代码将又臭又长,例如:
[self.view addConstraint: [NSLayoutConstraint constraintWithItem:AView
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:BView
attribute:NSLayoutAttributeLeft
multiplier:1
constant:0]];
仅表示AView的左侧距离BView的左侧一个单位,所以有必要引入一些第三方工具。
Masonry是一个轻量级的布局框架 拥有自己的描述语法 采用更优雅的链式语法封装自动布局 简洁明了 并具有高可读性 而且同时支持 iOS 和 Max OS X。
上面那句使用Mansory可以精简为:
[AView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(BView.left).with.offset(1);
}];
一个View的多个约束可以在同一个Block中实现,并且代码书写方式让人更容易理解。
更多使用技巧请戳:Masonry
2. Cell中对Layer的处理
其实cell中应避免一切对Layer的处理,包括圆角,阴影,甚至不应该包含任何透明View,因为这种渲染对系统的开销非常大,众多的Cell将使页面变的非???,在使用Layer时,也应该使用如下的方法减少以系统的开销。
self.layer.shouldRasterize = YES;
self.layer.rasterizationScale = [UIScreen mainScreen].scale;
CGPathRef path = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
[self.layer setShadowPath:path];
3. UITableViewCell中嵌套UITableView
看到网上有人说应该避免在UITableViewCell中使用UITableView,我觉得可以视需求的不同做不同的处理。对于一个模型结构非常复杂的TabeView,嵌套TableView可以降低代码的耦合,将不同的业务模型分散处理。只是需要注意的是,子TableView和父TableView的实现不应该在同一个文件中处理,也就是说delegate和dateSource不应该指向同一个对象,可以将子TableView封装成一个Cell,delegate和dataSource都交由这个Cell处理,这样才能有效降低代码的耦合,并且精简原文件的逻辑和大小。
4. UITableView中间层模型的封装
相信很多人会Cell的展示逻辑直接放到TableView的delegate中处理,例如:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (A) {
if(B){
return BHeight;
}
return AHeight;
}else if (C) {
if(B){
return BHeight;
}
return CHeight;
}
return 0;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(A){
if(B) {
return 2;
}
return 1;
}else if(C) {
if(B){
return 2;
}
return 1;
}
return 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(A) {
if(B) {
return BCell;
}
return ACell;
}else if(C) {
if(B){
return BCell;
}
return CCell;
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if(A) {
if(B) {
doB();
}
doA();
}else if(C) {
if(B){
doB();
}
doC();
}
}
所有代理中的逻辑都必须相同,而且同样的方法要写多次,例如上面的B。使用这样的方式,当遇到逻辑非常复杂的TableView时将使我们苦不堪言。TableView的代理应该只负责去构建Cell,而不应该来处理逻辑判断。所以,我们应该构建一个中间的模型层,在TableView reloadData的时候加载这个模型层,例如:
- (void)setupTableModel
{
if(A) {
if(B) {
[arrayModel addObject:BModel];
}
[arrayModel addObject:AModel];
}else if(C) {
if(B){
[arrayModel addObject:BModel];
}
[arrayModel addObject:CModel];
}
}
- (void)reloadTable
{
[self setupTableModel];
[tableView reloadData];
}
此时:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([[array objectAtIndex:row] isEqual:AModel]) {
return AHeight;
}else if ([[array objectAtIndex:row] isEqual:BModel]) {
return BHeight;
}else if ([[array objectAtIndex:row] isEqual:CModel]) {
return CHeight;
}
return 0;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return arrayModel.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([[array objectAtIndex:row] isEqual:AModel]) {
return ACell;
}else if ([[array objectAtIndex:row] isEqual:BModel]) {
return BCell;
}else if ([[array objectAtIndex:row] isEqual:CModel]) {
return CCell;
}
return nil;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([[array objectAtIndex:row] isEqual:AModel]) {
doA();
}else if ([[array objectAtIndex:row] isEqual:BModel]) {
doA();
}else if ([[array objectAtIndex:row] isEqual:CModel]) {
doA();
}
}
此时的delegate中只关注tableView的Cell构建和Cell行为,并不关心任何构建顺序等逻辑判断。