iOS开发——UI进阶篇(三)自定义不等高cell,如何拿到cell的行高,自动计算cell高度,(有配图,无配图)微博案例

时间:2023-12-04 21:32:32

一、纯代码自定义不等高cell


废话不多说,直接来看下面这个例子
先来看下微博的最终效果

iOS开发——UI进阶篇(三)自定义不等高cell,如何拿到cell的行高,自动计算cell高度,(有配图,无配图)微博案例

首先创建一个继承UITableViewController的控制器
@interface ViewController : UITableViewController
创建一个cell模型
@interface XMGStatusCell : UITableViewCell
再创建微博的数据模型
@interface XMGStatus : NSObject

纯代码自定义不等高cell

和前面等高cell的思路是一样的
1、创建子控件
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier;
2、布局子控件
等高与不等高的区别:不等高要动态的计算(lable或者image)的高度

// 计算不换行文字所占据的尺寸
NSDictionary *nameAttrs = @{NSFontAttributeName : XMGNameFont};
CGSize nameSize = [self.status.name sizeWithAttributes:nameAttrs];

// 计算换行文字所占据的尺寸
// CGFloat textH = [self.status.text sizeWithFont:XMGTextFont constrainedToSize:textMaxSize].height;
上面这个方法在ios7.0(ios2.0-7.0)已经过时了 进入头文件系统会提示你用最新的方法 “Use -boundingRectWithSize:options:attributes:context:”

NSDictionary *textAttrs = @{NSFontAttributeName : XMGTextFont};
CGFloat textH = [self.status.text boundingRectWithSize:textMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:textAttrs context:nil].size.height;

3、设置数据
重写模型数据的get方法

二、计算行高


这时运行程序会发现所有cell的高度都一样

而且等于storyboard内cell的高度

iOS开发——UI进阶篇(三)自定义不等高cell,如何拿到cell的行高,自动计算cell高度,(有配图,无配图)微博案例
因为从头到尾我们都没有用代码设置过高度,那么在哪里设置呢?
方案:在heightForRowAtIndexPath:方法调用之前将所有cell的高度计算清楚

 /**
* 返回每一行cell的具体高度
*/
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
XMGStatus *status = self.statuses[indexPath.row]; CGFloat margin = ;
CGFloat cellHeight = ; // 头像
CGFloat iconX = margin;
CGFloat iconY = margin;
CGFloat iconWH = ;
CGRect iconImageViewFrame = CGRectMake(iconX, iconY, iconWH, iconWH); // 文字
CGFloat textX = iconX;
CGFloat textY = CGRectGetMaxY(iconImageViewFrame) + margin;
CGFloat textW = [UIScreen mainScreen].bounds.size.width - * textX;
CGSize textMaxSize = CGSizeMake(textW, MAXFLOAT);
NSDictionary *textAttrs = @{NSFontAttributeName : [UIFont systemFontOfSize:]};
CGFloat textH = [status.text boundingRectWithSize:textMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:textAttrs context:nil].size.height;
CGRect text_labelFrame = CGRectMake(textX, textY, textW, textH); // 配图
if (status.picture) {
CGFloat pictureWH = ;
CGFloat pictureX = textX;
CGFloat pictureY = CGRectGetMaxY(text_labelFrame) + margin;
CGRect pictureImageViewFrame = CGRectMake(pictureX, pictureY, pictureWH, pictureWH); cellHeight = CGRectGetMaxY(pictureImageViewFrame);
} else {
cellHeight = CGRectGetMaxY(text_labelFrame);
} cellHeight += margin; return cellHeight;
}

这样就能达到案例的效果了

iOS开发——UI进阶篇(三)自定义不等高cell,如何拿到cell的行高,自动计算cell高度,(有配图,无配图)微博案例

虽然能解决上面的问题,但这样的代码看起来很垃圾,因为控制器知道的太多了,计算高度最好在你拿到数据的时候就已经计算好了,只要拿着用就行了
我们可以把计算高度封装到数据模型XMGStatus里

 /**
* 返回每一行cell的具体高度
*/
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
XMGStatus *status = self.statuses[indexPath.row];
return status.cellHeight;
} /*************XMGStatus*****************/
#import <UIKit/UIKit.h> @interface XMGStatus : NSObject
/**** 文字\图片数据 ****/
/** 姓名 */
@property (nonatomic, copy) NSString *name;
/** 文本 */
@property (nonatomic, copy) NSString *text;
/** 头像 */
@property (nonatomic, copy) NSString *icon;
/** 配图 */
@property (nonatomic, copy) NSString *picture;
/** 是否为会员 */
@property (nonatomic, assign) BOOL vip; /**** frame数据 ****/
/** 头像的frame */
@property (nonatomic, assign) CGRect iconFrame;
/** 昵称的frame */
@property (nonatomic, assign) CGRect nameFrame;
/** 会员的frame */
@property (nonatomic, assign) CGRect vipFrame;
/** 文字的frame */
@property (nonatomic, assign) CGRect textFrame;
/** 配图的frame */
@property (nonatomic, assign) CGRect pictureFrame;
/** cell的高度 */
@property (nonatomic, assign) CGFloat cellHeight; @end #import "XMGStatus.h" @implementation XMGStatus
- (CGFloat)cellHeight
{
if (_cellHeight == ) {
CGFloat margin = ; // 头像
CGFloat iconX = margin;
CGFloat iconY = margin;
CGFloat iconWH = ;
self.iconFrame = CGRectMake(iconX, iconY, iconWH, iconWH); // 昵称(姓名)
CGFloat nameY = iconY;
CGFloat nameX = CGRectGetMaxX(self.iconFrame) + margin;
// 计算文字所占据的尺寸
NSDictionary *nameAttrs = @{NSFontAttributeName : [UIFont systemFontOfSize:]};
CGSize nameSize = [self.name sizeWithAttributes:nameAttrs];
self.nameFrame = (CGRect){{nameX, nameY}, nameSize}; // 会员图标
if (self.vip) {
CGFloat vipW = ;
CGFloat vipH = nameSize.height;
CGFloat vipY = nameY;
CGFloat vipX = CGRectGetMaxX(self.nameFrame) + margin;
self.vipFrame = CGRectMake(vipX, vipY, vipW, vipH);
} // 文字
CGFloat textX = iconX;
CGFloat textY = CGRectGetMaxY(self.iconFrame) + margin;
CGFloat textW = [UIScreen mainScreen].bounds.size.width - * textX;
CGSize textMaxSize = CGSizeMake(textW, MAXFLOAT);
NSDictionary *textAttrs = @{NSFontAttributeName : [UIFont systemFontOfSize:]};
CGFloat textH = [self.text boundingRectWithSize:textMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:textAttrs context:nil].size.height;
self.textFrame = CGRectMake(textX, textY, textW, textH); // 配图
if (self.picture) {
CGFloat pictureWH = ;
CGFloat pictureX = textX;
CGFloat pictureY = CGRectGetMaxY(self.textFrame) + margin;
self.pictureFrame = CGRectMake(pictureX, pictureY, pictureWH, pictureWH); _cellHeight = CGRectGetMaxY(self.pictureFrame);
} else {
_cellHeight = CGRectGetMaxY(self.textFrame);
}
_cellHeight += margin;
}
return _cellHeight;
}
@end 那么我们在XMGStatusCell.m布局子控件就可以这样写
/**
* 布局子控件
*/
- (void)layoutSubviews
{
[super layoutSubviews]; self.iconImageView.frame = self.status.iconFrame;
self.nameLabel.frame = self.status.nameFrame;
self.vipImageView.frame = self.status.vipFrame;
self.text_label.frame = self.status.textFrame;
self.pictureImageView.frame = self.status.pictureFrame;
}
当然也可以直接在设置控件数据时布局(因为在给cell赋值时使用了setStatus:(XMGStatus *)status方法)
/**
* 设置子控件显示的数据
*/
- (void)setStatus:(XMGStatus *)status
{
_status = status; self.iconImageView.image = [UIImage imageNamed:status.icon];
self.nameLabel.text = status.name;
self.text_label.text = status.text; if (status.isVip) {
self.vipImageView.hidden = NO;
self.nameLabel.textColor = [UIColor orangeColor];
} else {
self.vipImageView.hidden = YES;
self.nameLabel.textColor = [UIColor blackColor];
} if (status.picture) {
self.pictureImageView.hidden = NO;
self.pictureImageView.image = [UIImage imageNamed:status.picture];
} else {
self.pictureImageView.hidden = YES;
} self.iconImageView.frame = status.iconFrame;
self.nameLabel.frame = status.nameFrame;
self.vipImageView.frame = status.vipFrame;
self.text_label.frame = status.textFrame;
self.pictureImageView.frame = status.pictureFrame;
}

运行程序效果就和案例一样

三、自定义不等高cell-storyboard(无配图)


除了代码自定义不等高cell,我们还可以直接用storyboard来自定义cell,相对来说就 简单很多,我们先来看下没有配图的情况
1、首先创建一个cell模型,设置好约束

iOS开发——UI进阶篇(三)自定义不等高cell,如何拿到cell的行高,自动计算cell高度,(有配图,无配图)微博案例

2、创建一个一个cell模型类,继承UITableViewCell,并且对应着cell模型连线,设置数据

 /*******************XMGStatusCell.m*********************/
#import "XMGStatusCell.h"
#import "XMGStatus.h" @interface XMGStatusCell()
/** 头像 */
@property (nonatomic, weak) IBOutlet UIImageView *iconImageView;
/** 名称 */
@property (nonatomic, weak) IBOutlet UILabel *nameLabel;
/** 会员图标 */
@property (nonatomic, weak) IBOutlet UIImageView *vipImageView;
/** 文字 */
@property (nonatomic, weak) IBOutlet UILabel *text_label;
@end @implementation XMGStatusCell /**
* 设置子控件显示的数据
*/
- (void)setStatus:(XMGStatus *)status
{
_status = status; self.iconImageView.image = [UIImage imageNamed:status.icon];
self.nameLabel.text = status.name;
self.text_label.text = status.text; if (status.vip) {
self.vipImageView.hidden = NO;
self.nameLabel.textColor = [UIColor orangeColor];
} else {
self.vipImageView.hidden = YES;
self.nameLabel.textColor = [UIColor blackColor];
}
}
@end

3、创建数据模型类XMGStatus,在控制器实现数据源方法;
值得一提的是在返回cell之前必须先告诉tableView所有cell的估算高度,那么可以在viewDidLoad中写上下面这句:
self.tableView.estimatedRowHeight = 44; // 估算每一行的高度
而且:必须告诉tableView所有cell的真实高度是自动计算(根据设置的约束来计算)
self.tableView.rowHeight = UITableViewAutomaticDimension;

iOS8开始:self-sizing

iOS开发——UI进阶篇(三)自定义不等高cell,如何拿到cell的行高,自动计算cell高度,(有配图,无配图)微博案例

如果没写这两句,运行出来的高度都是不对的

 #import "ViewController.h"
#import "XMGStatus.h"
#import "MJExtension.h"
#import "XMGStatusCell.h" @interface ViewController ()
/** 微博数据 */
@property (nonatomic, strong) NSArray *statuses;
@end @implementation ViewController NSString *ID = @"status"; - (NSArray *)statuses
{
if (!_statuses) {
_statuses = [XMGStatus objectArrayWithFilename:@"statuses.plist"];
}
return _statuses;
} - (void)viewDidLoad {
[super viewDidLoad]; // 告诉tableView所有cell的真实高度是自动计算(根据设置的约束来计算)
self.tableView.rowHeight = UITableViewAutomaticDimension;
// 告诉tableView所有cell的估算高度
self.tableView.estimatedRowHeight = ;
} #pragma mark - <数据源>
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.statuses.count;
} - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
XMGStatusCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; cell.status = self.statuses[indexPath.row]; return cell;
}
@end

四、自定义不等高cell-storyboard(有配图)


有图片和无图片其实一样,重点在于如何自动计算行高
1、首先,cell模型里再添加imageView(配图)
2、然后,在 XMGStatusCell.m 内添加三个属性
/** 配图 */
@property (nonatomic, weak) IBOutlet UIImageView *pictureImageView;
/** 配图的高度约束 */
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *pictureHeight;
/** 配图底部间距约束 */
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *pictureBottom;
3、设置数据

XMGStatusCell.h

 #import <UIKit/UIKit.h>
@class XMGStatus; @interface XMGStatusCell : UITableViewCell
/** 模型数据 */
@property (nonatomic, strong) XMGStatus *status;
@end

XMGStatusCell.m

 /**
* 设置子控件显示的数据
*/
- (void)setStatus:(XMGStatus *)status
{
_status = status; self.iconImageView.image = [UIImage imageNamed:status.icon];
self.nameLabel.text = status.name;
self.text_label.text = status.text; if (status.vip) {
self.vipImageView.hidden = NO;
self.nameLabel.textColor = [UIColor orangeColor];
} else {
self.vipImageView.hidden = YES;
self.nameLabel.textColor = [UIColor blackColor];
} // 设置配图数据
if (status.picture) { // 有配图
self.pictureHeight.constant = ;
self.pictureBottom.constant = ;
self.pictureImageView.image = [UIImage imageNamed:status.picture];
} else { // 没有配图
// 设置图片高度为0
self.pictureHeight.constant = ;
// 设置图片底部间距为0
self.pictureBottom.constant = ;
}
}

XMGStatus.h

 #import <UIKit/UIKit.h>

 @interface XMGStatus : NSObject
/**** 文字\图片数据 ****/
/** 姓名 */
@property (nonatomic, copy) NSString *name;
/** 文本 */
@property (nonatomic, copy) NSString *text;
/** 头像 */
@property (nonatomic, copy) NSString *icon;
/** 配图 */
@property (nonatomic, copy) NSString *picture;
/** 是否为会员 */
@property (nonatomic, assign) BOOL vip;
@end

XMGStatus.m

 #import "XMGStatus.h"

 @implementation XMGStatus

 @end
 #import "ViewController.h"
#import "XMGStatus.h"
#import "MJExtension.h"
#import "XMGStatusCell.h" @interface ViewController ()
/** 微博数据 */
@property (nonatomic, strong) NSArray *statuses;
@end @implementation ViewController NSString *ID = @"status"; - (NSArray *)statuses
{
if (!_statuses) {
_statuses = [XMGStatus objectArrayWithFilename:@"statuses.plist"];
}
return _statuses;
} - (void)viewDidLoad {
[super viewDidLoad]; // iOS8开始:self-sizing // 告诉tableView所有cell的真实高度是自动计算(根据设置的约束来计算)
self.tableView.rowHeight = UITableViewAutomaticDimension;
// 告诉tableView所有cell的估算高度
self.tableView.estimatedRowHeight = ;
} #pragma mark - <数据源>
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.statuses.count;
} - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
XMGStatusCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; cell.status = self.statuses[indexPath.row]; return cell;
} @end

运行结果

iOS开发——UI进阶篇(三)自定义不等高cell,如何拿到cell的行高,自动计算cell高度,(有配图,无配图)微博案例

五、最终代码


在以前ios开发中,经常会发现程序在运行前屏幕会黑屏一会,这是为什么呢?我们这里也存在类似问题,因为在程序运行前会要显示一部分cell,苹果会提前将每一个cell的高度都算好,而且内部一些运行也需要调用这个方法,总之,当我们cell特别多时,这个方法的调用会特别频繁,就会出现黑屏一会的情况
1、解决方案:
告诉tableView所有cell的估算高度(设置了估算高度,就可以减少tableView:heightForRowAtIndexPath:方法的调用次数)
self.tableView.estimatedRowHeight = 200;

有些公司的项目还是以前的老项目,没有用到IOS8,那么计算高度可以用下面这种方法解决

2、返回高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
上面这两个方法调用顺序依次是先计算高度再返回cell,也就是说应该在返回cell前将高度算好

 #import "ViewController.h"
#import "XMGStatus.h"
#import "MJExtension.h"
#import "XMGStatusCell.h" @interface ViewController ()
/** 微博数据 */
@property (nonatomic, strong) NSArray *statuses;
@end @implementation ViewController NSString *ID = @"status"; - (NSArray *)statuses
{
if (!_statuses) {
_statuses = [XMGStatus objectArrayWithFilename:@"statuses.plist"];
}
return _statuses;
} - (void)viewDidLoad {
[super viewDidLoad];
// 告诉tableView所有cell的估算高度(设置了估算高度,就可以减少tableView:heightForRowAtIndexPath:方法的调用次数)
self.tableView.estimatedRowHeight = ;
} #pragma mark - <数据源>
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.statuses.count;
} - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
XMGStatusCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; cell.status = self.statuses[indexPath.row]; return cell;
} #pragma mark - <代理方法>
XMGStatusCell *cell;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{ // 创建一个cell(cell的作用:根据模型数据布局所有的子控件,进而计算出cell的高度)
if (!cell) {
cell = [tableView dequeueReusableCellWithIdentifier:ID];
}
// 设置模型数据
cell.status = self.statuses[indexPath.row];
return cell.height;
}
@end
/****************** XMGStatusCell.m **********************/
#import "XMGStatusCell.h"
#import "XMGStatus.h" @interface XMGStatusCell()
/** 头像 */
@property (nonatomic, weak) IBOutlet UIImageView *iconImageView;
/** 名称 */
@property (nonatomic, weak) IBOutlet UILabel *nameLabel;
/** 会员图标 */
@property (nonatomic, weak) IBOutlet UIImageView *vipImageView;
/** 文字 */
@property (nonatomic, weak) IBOutlet UILabel *text_label;
/** 配图 */
@property (nonatomic, weak) IBOutlet UIImageView *pictureImageView;
@end @implementation XMGStatusCell - (void)awakeFromNib
{
// 如果lable有自动换行的情况时
// 手动设置文字的最大宽度(目的是:让label知道自己文字的最大宽度,进而能够计算出自己的frame)
self.text_label.preferredMaxLayoutWidth = [UIScreen mainScreen].bounds.size.width - ;
} /**
* 设置子控件显示的数据
*/
- (void)setStatus:(XMGStatus *)status
{
_status = status; self.iconImageView.image = [UIImage imageNamed:status.icon];
self.nameLabel.text = status.name;
self.text_label.text = status.text; if (status.vip) {
self.vipImageView.hidden = NO;
self.nameLabel.textColor = [UIColor orangeColor];
} else {
self.vipImageView.hidden = YES;
self.nameLabel.textColor = [UIColor blackColor];
} // 设置配图数据
if (status.picture) { // 有配图
self.pictureImageView.hidden = NO;
self.pictureImageView.image = [UIImage imageNamed:status.picture];
} else { // 没有配图
self.pictureImageView.hidden = YES;
}
} - (CGFloat)height
{
// 强制布局cell内部的所有子控件(label根据文字多少计算出自己最真实的尺寸)
[self layoutIfNeeded]; // 计算cell的高度
if (self.status.picture) {
return CGRectGetMaxY(self.pictureImageView.frame) + ;
} else {
return CGRectGetMaxY(self.text_label.frame) + ;
}
}
@end

iOS开发——UI进阶篇(三)自定义不等高cell,如何拿到cell的行高,自动计算cell高度,(有配图,无配图)微博案例