不等高cell纯代码:
对应的GitHub 项目链接:https://github.com/liminting/CalculateCellDemo
不等高cell
- 先设置高度rowH = 250
- 复习frame设置等高cell
1.头像:宽高都是30,距离top.left都是10
CGFloat iconX = ;
CGFloat iconY = ;
CGFloat iconW = ;
CGFloat iconH = ;
self.icon_Ima.frame = CGRectMake(iconX, iconY, iconW, iconH);
2.昵称:left距离icon_Ima为10,顶部和icon_Ima对齐,宽高根据文字大小决定
CGFloat nameX = ;
CGFloat nameY = ;
CGFloat nameW = ;
CGFloat nameH = ;
self.name_Lab.frame = CGRectMake(nameX, nameY, nameW, nameH);
3.vip:宽高14,距离昵称10,垂直方向与昵称中线对齐
CGFloat vipX = ;
CGFloat vipY = ;
CGFloat vipW = ;
CGFloat vipH = ;
self.vip_Ima.frame = CGRectMake(vipX, vipY, vipW, vipH);
4.正文:左边与头像对齐,右边距离与左边距离相同,顶部距离头像间距10,高度根据文字多少决定
CGFloat textX = ;
CGFloat textY = ;
CGFloat textW = ;
CGFloat textH = ;
self.text_Lab.frame = CGRectMake(textX, textY, textW, textH);
5.配图:宽高100,左边与正文对齐,顶部距离正文距离为10
CGFloat pictureX = ;
CGFloat pictureY = ;
CGFloat pictureW = ;
CGFloat pictureH = ;
self.picture_Ima.frame = CGRectMake(pictureX, pictureY, pictureW, pictureH);
BetterCellH
优化cellH的计算
reason: 代理方法heightForRowAtIndexPath以及cell布局方法layoutSubviews存在大量重复性代码,必须抽取出来
解决办法: remove其中较"晚"调用方法中的计算,将heightForRowAtIndexPath中的计算通过模型传递给之后用到的地方:cell布局方法layoutSubviews
- 1.模型中新添加frames属性
@property (nonatomic, assign) CGRect icon_Ima_frame; /**< 头像frame */
@property (nonatomic, assign) CGRect name_Lab_frame; /**< 昵称frame */
@property (nonatomic, assign) CGRect vip_Ima_frame; /**< vipframe */
@property (nonatomic, assign) CGRect text_Lab_frame; /**< 正文frame */
@property (nonatomic, assign) CGRect picture_Ima_frame; /**< 配图frame */
- 2.heightForRowAtIndexPath代理方法中计算所有子控件frame
根据indexPath返回cell的高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"%s, line = %d, row = %ld", __FUNCTION__, __LINE__, indexPath.row);
XMGStatus *status = self.statuses[indexPath.row];
在这里先计算好cell的高度,然后返回
CGFloat margin = 10;
1.头像:宽高都是30,距离top.left都是10
CGFloat iconX = margin;
CGFloat iconY = margin;
CGFloat iconW = 30;
CGFloat iconH = iconW;
CGRect icon_Ima_frame = CGRectMake(iconX, iconY, iconW, iconH);
status.icon_Ima_frame = icon_Ima_frame;
2.昵称:left距离icon_Ima为10,顶部和icon_Ima对齐,宽高根据文字大小决定
CGFloat nameX = CGRectGetMaxX(icon_Ima_frame) + margin;
CGFloat nameY = iconY;
根据字体大小计算单行文字的size
CGSize nameSize = [status.name sizeWithAttributes:@{NSFontAttributeName: NameFont}];
CGFloat nameW = nameSize.width;
CGFloat nameH = nameSize.height;
CGRect name_Lab_frame = CGRectMake(nameX, nameY, nameW, nameH);
status.name_Lab_frame = name_Lab_frame;
3.vip:宽高14,距离昵称10,垂直方向与昵称中线对齐
CGFloat vipX = CGRectGetMaxX(name_Lab_frame) + margin;
CGFloat vipW = 14;
CGFloat vipH = 14;
CGFloat vipY = CGRectGetMidY(name_Lab_frame) - vipH * 0.5;
CGRectGetMidY(self.name_Lab.frame) = vipY + vipH * 0.5
CGRect vip_Ima_frame = CGRectMake(vipX, vipY, vipW, vipH);
status.vip_Ima_frame = vip_Ima_frame;
4.正文:左边与头像对齐,右边距离与左边距离相同,顶部距离头像间距10,高度根据文字多少决定
CGFloat textX = iconX;
CGFloat textY = margin + CGRectGetMaxY(icon_Ima_frame);
CGFloat textW = self.tableView.bounds.size.width - 2 * textX;
CGSize maxSize = CGSizeMake(textW, CGFLOAT_MAX);
CGSize textSize = [status.text boundingRectWithSize:maxSize
options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:
TextFont} context:nil].size;
CGFloat textH = textSize.height;
CGRect text_Lab_frame = CGRectMake(textX, textY, textW, textH);
status.text_Lab_frame = text_Lab_frame;
5.配图:宽高100,左边与正文对齐,顶部距离正文距离为10
CGRect picture_Ima_frame = CGRectZero;
if (status.picture) {
CGFloat pictureW = 100;
CGFloat pictureH = 100;
CGFloat pictureX = textX;
CGFloat pictureY = margin + CGRectGetMaxY(text_Lab_frame);
picture_Ima_frame = CGRectMake(pictureX, pictureY, pictureW, pictureH);
}
status.picture_Ima_frame = picture_Ima_frame;
CGFloat CellH = (status.picture)? CGRectGetMaxY(picture_Ima_frame) + margin: CGRectGetMaxY(text_Lab_frame) + margin;
return CellH;
}
- 3.由模型传入cell,在 layoutSubviews 中完成布局
3.在layoutSubviews方法中布局
- (void)layoutSubviews
{
[super layoutSubviews];
self.icon_Ima.frame = self.status.icon_Ima_frame;
self.name_Lab.frame = self.status.name_Lab_frame;
self.vip_Ima.frame = self.status.vip_Ima_frame;
self.text_Lab.frame = self.status.text_Lab_frame;
self.picture_Ima.frame = self.status.picture_Ima_frame;
}
总结:
- 重复计算问题解决
- 代理方法调用频繁,实际问题仍然严峻
根据问题继续优化
- 思考每个cell对应一个模型
- 在模型中新增属性cellH@property (nonatomic, assign) CGFloat cellH; /**< cell的高度 */
- 重写get方法,懒加载中计算
懒加载
- (CGFloat)cellH
{
if (!_cellH) {
在这里先计算好cell的高度,然后返回呢?
NSLog(@"%s, line = %d", __FUNCTION__, __LINE__);
...
_cellH = (self.picture)? CGRectGetMaxY(picture_Ima_frame) + margin: CGRectGetMaxY(text_Lab_frame) + margin;
}
return _cellH;
}
- 此时控制器中代理方法知道的很少
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
XMGStatus *status = self.statuses[indexPath.row];
return status.cellH;
}
additional(了解)
- cell中的布局可以在模型的set方法中写
- 新建XMGStatusFrame类
* Frame相关属性在声明文件中是只读的:
@property (nonatomic, strong) XMGStatus *status; /**< 微博模型 */
@property (nonatomic, assign, readonly) CGRect icon_Ima_frame; /**< 头像frame */
@property (nonatomic, assign, readonly) CGRect name_Lab_frame; /**< 昵称frame */
@property (nonatomic, assign, readonly) CGRect vip_Ima_frame; /**< vipframe */
@property (nonatomic, assign, readonly) CGRect text_Lab_frame; /**< 正文frame */
@property (nonatomic, assign, readonly) CGRect picture_Ima_frame; /**< 配图frame */
@property (nonatomic, assign, readonly) CGFloat cellH; /**< cell的高度 */
- 在status模型的set方法中实现只读属性的计算
#import "XMGStatusFrame.h"
@interface XMGStatusFrame ()
@property (nonatomic, assign) CGRect icon_Ima_frame; /**< 头像frame */
@property (nonatomic, assign) CGRect name_Lab_frame; /**< 昵称frame */
@property (nonatomic, assign) CGRect vip_Ima_frame; /**< vipframe */
@property (nonatomic, assign) CGRect text_Lab_frame; /**< 正文frame */
@property (nonatomic, assign) CGRect picture_Ima_frame; /**< 配图frame */
@property (nonatomic, assign) CGFloat cellH; /**< cell的高度 */
@end
@implementation XMGStatusFrame
- (void)setStatus:(XMGStatus *)status
{
_status = status;
CGFloat margin = 10;
1.头像:宽高都是30,距离top.left都是10
CGFloat iconX = margin;
CGFloat iconY = margin;
CGFloat iconW = 30;
CGFloat iconH = iconW;
CGRect icon_Ima_frame = CGRectMake(iconX, iconY, iconW, iconH);
self.icon_Ima_frame = icon_Ima_frame;
2.昵称:left距离icon_Ima为10,顶部和icon_Ima对齐,宽高根据文字大小决定
CGFloat nameX = CGRectGetMaxX(icon_Ima_frame) + margin;
CGFloat nameY = iconY;
根据字体大小计算单行文字的size
CGSize nameSize = [status.name sizeWithAttributes:@{NSFontAttributeName: NameFont}];
CGFloat nameW = nameSize.width;
CGFloat nameH = nameSize.height;
CGRect name_Lab_frame = CGRectMake(nameX, nameY, nameW, nameH);
self.name_Lab_frame = name_Lab_frame;
3.vip:宽高14,距离昵称10,垂直方向与昵称中线对齐
CGFloat vipX = CGRectGetMaxX(name_Lab_frame) + margin;
CGFloat vipW = 14;
CGFloat vipH = 14;
CGFloat vipY = CGRectGetMidY(name_Lab_frame) - vipH * 0.5; // CGRectGetMidY(self.name_Lab.frame) = vipY + vipH * 0.5
CGRect vip_Ima_frame = CGRectMake(vipX, vipY, vipW, vipH);
self.vip_Ima_frame = vip_Ima_frame;
4.正文:左边与头像对齐,右边距离与左边距离相同,顶部距离头像间距10,高度根据文字多少决定
CGFloat textX = iconX;
CGFloat textY = margin + CGRectGetMaxY(icon_Ima_frame);
CGFloat textW = [UIScreen mainScreen].bounds.size.width - 2 * textX;
CGSize maxSize = CGSizeMake(textW, CGFLOAT_MAX);
CGSize textSize = [status.text boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: TextFont} context:nil].size;
CGFloat textH = textSize.height;
CGRect text_Lab_frame = CGRectMake(textX, textY, textW, textH);
self.text_Lab_frame = text_Lab_frame;
5.配图:宽高100,左边与正文对齐,顶部距离正文距离为10
CGRect picture_Ima_frame = CGRectZero;
if (status.picture) {
CGFloat pictureW = 100;
CGFloat pictureH = 100;
CGFloat pictureX = textX;
CGFloat pictureY = margin + CGRectGetMaxY(text_Lab_frame);
picture_Ima_frame = CGRectMake(pictureX, pictureY, pictureW, pictureH);
}
self.picture_Ima_frame = picture_Ima_frame;
self.cellH = (status.picture)? CGRectGetMaxY(picture_Ima_frame) + margin: CGRectGetMaxY(text_Lab_frame) + margin;
}
- 删除XMGStatus中的多余属性,完善模型属性set方法
- (void)setIcon:(UIImage *)icon
{
if ([icon isKindOfClass:[NSString class]]) {
_icon = [UIImage imageNamed:(NSString *)icon];
}else
{
_icon = icon;
}
}
- (void)setVip:(id)vip
{
if ([vip isKindOfClass:[NSNumber class]]) {
_vip = [vip boolValue];
}else
{
_vip = vip;
}
}
- (void)setPicture:(UIImage *)picture
{
if ([picture isKindOfClass:[NSString class]]) {
_picture = (picture)? [UIImage imageNamed:(NSString *)picture]: nil;
}else
{
_picture = picture;
}
}
- 修改控制器中的模型数组为 statusFrames
懒加载
- (NSArray *)statusFrames
{
if (!_statusFrames) {
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"statuses.plist" ofType:nil];
NSArray *dicts = [NSArray arrayWithContentsOfFile:filePath];
NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:dicts.count];
for (NSDictionary *dict in dicts) {
XMGStatus *obj = [XMGStatus statusWithDict:dict];
转换代码如下
XMGStatusFrame *statusFrame = [[XMGStatusFrame alloc] init];
statusFrame.status = obj;
[arrayM addObject:statusFrame];
}
_statusFrames = [arrayM copy];
}
return _statusFrames;
}
- 修改cell中的模型属性为statusFrame
2.在模型的set方法中为子控件赋值数据
- (void)setStatusFrame:(XMGStatusFrame *)statusFrame
{
_statusFrame = statusFrame;
XMGStatus *status = statusFrame.status;
1.头像
self.icon_Ima.image = status.icon;
2.昵称
self.name_Lab.text = status.name;
self.name_Lab.textColor = status.isVip ? [UIColor orangeColor]: [UIColor blackColor];
3.vip
self.vip_Ima.hidden = !status.isVip;
4.正文
self.text_Lab.text = status.text;
5.配图
if (status.picture) {
self.picture_Ima.hidden = NO;
self.picture_Ima.image = status.picture;
}else
{
self.picture_Ima.hidden = YES;
self.picture_Ima.image = nil;
}
可以将frame在模型的方法中提前设置好,这样不用重写layoutSubviews方法了
self.icon_Ima.frame = statusFrame.icon_Ima_frame;
self.name_Lab.frame = statusFrame.name_Lab_frame;
self.vip_Ima.frame = statusFrame.vip_Ima_frame;
self.text_Lab.frame = statusFrame.text_Lab_frame;
self.picture_Ima.frame = statusFrame.picture_Ima_frame;
}
- 完善数据源代理方法中的赋值
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.statusFrames.count;
}
在cell显示到屏幕上之前,cell的高度就已经被确定
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
拿到cell
XMGStatusCell *cell = [XMGStatusCell cellWithTableView:tableView];
覆盖数据
cell.statusFrame = self.statusFrames[indexPath.row];
return cell;
}
pragma mark - UITableViewDelegate
warning 当tableView加载的时候,在cell显示到屏幕上之前(cell创建之前),tableView会先拿到所有的cell的高度,来确定contentSize用来估算右边索引条的高度,所以在创建cell的时候,一定是要知道cell高度的
根据indexPath返回cell的高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
XMGStatusFrame *statusFrame = self.statusFrames[indexPath.row];
return statusFrame.cellH;
}
不等高cell-Autolayout-iOS8+
了解估算高度
- 多添加一个最后一个子控件距离contentView底部的约束
- 在viewDidLoad方法中
先设置估算高度,tableView刚开始显示的滚动条就可以根据估算高度得到contentSize
self.tableView.estimatedRowHeight = 250;
取消storyboard中行高的设置
self.tableView.rowHeight = UITableViewAutomaticDimension;
- 在有配图的情况下,拖线设置新添加约束的值即可
微博正文距离底部的约束
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *textToBottom;
5.配图
if (status.picture) {
self.picture_Ima.hidden = NO;
self.picture_Ima.image = status.picture;
self.textToBottom.constant = 120;
}else
{
self.picture_Ima.hidden = YES;
self.picture_Ima.image = nil;
self.textToBottom.constant = 10;
}
不等高cell-Autolayout-iOS6,7
首先恢复高度250的等高cell
- 去掉最后底部的约束以及代码
- 去掉self-sizing的两行代码
先设置估算高度,tableView刚开始显示的滚动条就可以根据估算高度得到contentSize
self.tableView.estimatedRowHeight = 250;
行高是由约束自动决定是多少
self.tableView.rowHeight = UITableViewAutomaticDimension;
分析方法调用顺序
- 再次启用代理方法 heightForRowAtIndexPath
* 创建一个临时的 tempCell
* 强制布局
* 解决依赖于屏幕宽度的 text_Lab
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
要在此处得到显示在屏幕上的cell的高度
拿到cell
XMGStatusCell *tempCell = [XMGStatusCell cellWithTableView:tableView];
覆盖数据
tempCell.status = self.statuses[indexPath.row];
需要在布局前,对我们多行cell显示的最大宽度设置限制
[tempCell.text_Lab setPreferredMaxLayoutWidth:tableView.bounds.size.width - 20];
warning 由于我们创建的tempCell不是显示到屏幕上的,所以父view没有宽度,导致原先的约束失效,我们的Autolayout没有布局成frame
强制布局
[tempCell layoutIfNeeded];
[tempCell.text_Lab setPreferredMaxLayoutWidth:tableView.bounds.size.width - 20];
[tempCell layoutIfNeeded];
CGFloat cellH = (tempCell.status.picture) ? CGRectGetMaxY(tempCell.picture_Ima.frame) :CGRectGetMaxY(tempCell.text_Lab.frame);
cellH += 10;
return cellH;
}
- 分析总结:
* cell.text_Lab.宽度约束 = cell.contentView的宽度 - 20
* 由于我们创建的tempCell不显示到屏幕上的,所以cell.contentView宽度不确定,导致原先的约束无效,我们的Autolayout没有布局成frame
* 解决办法(2种):
* 1.设置text_Lab的最大宽度
[tempCell.text_Lab setPreferredMaxLayoutWidth:tableView.bounds.size.width - 20];
* 2.为text_Lab的宽度约束拖一条线手动设置
手动设置了有效的,不依赖于父view的约束
self.textW.constant = [UIScreen mainScreen].bounds.size.width - 20;
- 性能优化
* heightForRowAtIndexPath`调用频繁
* tempCell 只需要创建一次
* 封装cellH 的计算
* 设置估算高度 self.tableView.estimatedRowHeight = 250;
XMGStatusCell *tempCell;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
要在此处得到显示在屏幕上的cell的高度
拿到cell
if (!tempCell) {
tempCell = [XMGStatusCell cellWithTableView:tableView];
NSLog(@"%p, %s, line = %d", tempCell, __FUNCTION__, __LINE__);
}
覆盖数据
tempCell.status = self.statuses[indexPath.row];
return tempCell.cellH;
}
- 估算高度的作用:让 heightForRowAtIndexPath 懒加载
- 代理方法
tableView用估算高度快速计算估算值来节省加载时间,如果这些方法实现,那我们显示在屏幕上的cell才会调用cellForRow
Use the estimatedHeight methods to quickly calcuate guessed values which will allow for fast load times of the table.
If these methods are implemented, the above -tableView:heightForXXX calls will be deferred until views are ready to be displayed, so more expensive logic can be placed there.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForFooterInSection:(NSInteger)section NS_AVAILABLE_IOS(7_0);