我的代码分离
先说明一个前提:这里并没有严格的MVVM或者其他的设计模式。但是也能很好的做到分离控制器的代码、表格的代理方法被复用。
实现初衷
- 将业务逻辑与控制器进行分离,一些通用的逻辑都能够被复用进行自动的处理,如无数据的处理等。
- 表格控制器的代理事件整个项目只有一份。
文件介绍
BaseTableViewCellProtocol // 声明cell、分区头尾视图进行数据配置的方法,子类cell进行覆写即能完成cell的配置
BaseTableViewCell // 基类cell,所有的cell需要继承他,并用其类方法获取复用标识
BaseTableViewSectionHeaderFooterView // 基类分区头尾视图,所有的自定义分区头尾视图需要继承他,并用其类方法获取复用标识
BaseTableViewDisplayModel // 控制表格的具体显示,每一个对象代表一个分区,其内部的数组为cell的数据源,子类可以继承,完成自定义的样式
BaseTableViewModel // 实现了表格的代理方法,其内部的数组为整个表格的数据源,即一个或多个BaseTableViewDisplayModel对象,子类可以继承,完成自定义的逻辑
实现后的部分代码
创建BaseTableViewModel
@interface TestViewController1 ()
/// 表格视图
@property (nonatomic, weak) UITableView *tableView;
/// viewModel
@property (nonatomic, strong) TestViewModel1 *viewModel;
@end
@implementation TestViewController1
- (void)viewDidLoad {
[super viewDidLoad];
// 设置标题
self.navigationItem.title = NSStringFromClass(self.class);
// 创建表格
_tableView = [CreateControlUtil tableViewWithFrame:self.view.bounds style:UITableViewStylePlain];
[_tableView registerClass:TestTableViewCell.class forCellReuseIdentifier:[TestTableViewCell reusedIndentifire]];
[self.view addSubview:_tableView];
// 创建ViewModel
_viewModel = [[TestViewModel1 alloc] initWithTableView:_tableView];
// 设置刷新
WeakBlock(weakSelf);
_tableView.yt_RefreshHeaderBlock = ^{
weakSelf.tableView.pageIndex = 0;
[weakSelf.viewModel beginRequestData];
};
[_tableView yt_headerBeginRefreshing];
}
@end
自行创建的ViewModel代码:
@implementation TestViewModel1
#pragma mark - OverLoad
/**
该方法覆写父类的实现,子类无需声明
*/
- (void)beginRequestData {
[SVProgressHUD show];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
for (int i = 0; i < 5; i++) {
BaseTableViewDisplayModel *displayModel = [[BaseTableViewDisplayModel alloc] init];
// 设置cell,这里是固定的高度
[displayModel.innerDatas addObjectsFromArray:@[@"Cell1", @"Cell2", @"Cell3"]];
displayModel.cellClassName = @"TestTableViewCell";
displayModel.cellHeight = 50;
// 设置分区头
displayModel.sectionHeaderHeight = 40.0;
displayModel.sectionHeaderData = [NSString stringWithFormat:@"分区头%d", i];
displayModel.sectionHeaderClassName = @"TestSectionHeaderFooterView";
// 设置分区尾
displayModel.sectionFooterHeight = 40.0;
displayModel.sectionFooterData = [NSString stringWithFormat:@"分区尾%d", i];
displayModel.sectionFooterClassName = @"TestSectionHeaderFooterView";
[self.tableViewDatas addObject:displayModel];
}
[self.tableView doneLoadingData:5];
});
}
@end
Cell的配置
/**
覆写父类的方法
*/
- (void)setCellWidthData:(id)data {
/// 你在设计cell的时候,应该预先知道数据源的类型
if (![data isKindOfClass:NSString.class]) return;
_testLabel.text = data;
[self setSeparatorLeftInset:20.0 rightInset:20.0];
}
也可以不创建ViewModel,使用BaseTableViewModel即可,只是把ViewModel中的逻辑放到控制器中,按照规定添加数据即可。
使用规则
- 注册cell:使用基类的获取复用标识的类方法进行初始化。
- 初始化ViewModel:使用TableView进行初始化(虽然不太想ViewModel引用UI层的内容)。
- 创建一个displayModel对象:一个分区一个对象,并添加至ViewModel的tableViewDatas数组中。
- 设置cell:设置高度(分为固定和非固定)、设置类名、设置数据源(displayModel的innerDatas数组)。
- 设置分区头分区尾:设置高度、设置类名、设置数据源。
Cell的高度问题
首先一个小小的建议:label的高度完全可以固定为fontSize - 1,当然对于fontSize实时变化的就另当别论了。
cell的高度一般都是可以固定的,直接给displayModel的cellHeight赋值即可,如果是动态高度需要将高度计算好并添加到displayModel的cellHeights数组当中去。
强烈建议使用XIB布局cell,这种方式布局cell比较简单方便,最主要的是可以通过约束完成cell高度的自动计算。
Cell高度的自动计算
你的cell必须使用约束进行布局,并进行如下设置:
tableView.estimatedRowHeight = 85.0; // 这是一个预估值,预估值越精确效果越好
tableView.rowHeight = UITableViewAutomaticDimension;
添加约束需要注意的点:
1、如果写了表格获取cell高度的代理方法,返回一个负数是最好的(亲测),如果返回正数可能导致高度计算不准确。
2、为了系统能够正确的计算出cell的高度,你必须设置连续的约束让cell被完全填充,并且子视图距离cell上下左右的边距都需要被指定。
如何完成如下两个例子中cell高度的计算呢:
例1:
1、为ImageView添加好约束:上边距、居中、宽、高。
2、为Name添加好约束:上边距、居中、宽、高。
3、为Label添加好约束:上边距、下边距、左右边距。 高度由系统计算
至此完成了约束,距离cell上下左右的边距都已经被指定。
例2:
1、为Label1添加好约束:上边距、左右边距。高度由系统计算
2、为Label2添加好约束:上边距、左右边距。高度由系统计算
2、为Label3添加好约束:上下边距、左右边距。高度由系统计算
但是走到了这里会发现XIB直接报错了:
这里涉及到另外两个概念:intrinsic Content、CHCR。可以直接看我的总结。
另外贴出两篇文章:官方介绍、实践教程。虽然有了这两篇文章,但可能还是不知道怎么用。
对于具有intrinsic Content属性的视图在添加约束的时候,可以只设置point而不用设置size,系统会自动根据intrinsic Content进行size的计算。比如UILabel的intrinsic Content根据文本进行计算、UIButton的根据标题文本进行计算、UIImageView的根据image的尺寸进行计算。所以上面大部分的label我都没有添加高度的约束。
如果UILabel要根据文本自动进行宽高的计算,那么这个过程中势必会进行label的拉伸或者压缩,而且拉伸和压缩又分为水平方向和垂直方向。每一个视图都具备Content-Hugging-Priority和Compression-Resistance-Priority(CHCR)属性决定着它本身被拉伸、压缩的优先级。
Content-Hugging为了阻止视图被拉伸,值越大则会越晚被拉伸。Compression-Resistance为了阻止视图被压缩,值越大则会越晚被压缩。这两个值可以直接在XIB中进行设置。
那么问题来了,如果存在多个需要被拉伸、压缩的视图,那么这视图之间就存在竞争关系,系统必须知道哪个视图先被拉伸压缩、哪个视图后被拉伸压缩。
回到例子当中来,例子1并不存在竞争关系,所以在设置完约束后并不报错,而例子2中的3个label之间存在竞争关系。例子2中的3个label均需要在垂直方向上做拉伸,那么必须明确他们之间的优先级,理想的拉伸顺序是Label1、Label2、Label3。
在XIB中可以看到如下面板:
我们现在只需要修改Content-Hugging-Priority-Vertical的值来控制拉伸顺序就好,例如将3个label的该值分别设置为200、220、230。运行程序,发现完美的解决了高度问题。 但是告诉大家一个不幸的消息是:通过控制CHCR在iOS9以前是行不通的,但是即便这样,当你明白了其中的原理,我相信cell的高度计算早已不是问题。
Demo
关于Demo,如果有需要,我会给出。