在讲自定义不等高cell之前先了解一下静态cell 和 动态cell.
动态cell 和 静态cell 都是在UITableViewController的storyboard中操作!
动态cell : 在storyboard 创建一个UITableView的控制器,默认就是动态的cell,所谓动态cell就是在程序运行后,cell的个数以及
cell中显示的内容都是由数据源决定的.
静态cell: 显示的内容可以在storyboard设置,并定死,在storyboard中设置的什么内容,运行出来就是什么内容.
(比如手机的设置界面就可以用静态cell来做)
注意: 静态的cell也可以使用代理方法去监听静态cell的事件
在静态的cell中,也可以自定义cell,不过需要把style改为custom,如果使用系统的样式,可以把style改为basic
通过纯代码的方式自定义不等高cell效果图如下:
从上面的效果图中可以看出:
每一个cell的高度都是不同的,计算cell的高度也是本文中的一个重点及难点!
项目需求:
以微博为例,当别人发状态时,有的人发文字,有的人发文字和图片,那么如何实现这种方式呢?
项目思路:
在UITableViewCell中的系统样式中不能满足项目所需,那么我们首先得想,通过自定义cell的方式来完成,至于高度不等,那么我们可以在每一个cell中通过判断哪个控件是最后一个,然后就取它的高度然后和cell底部间留有间距就完成了。
项目实现过程:
1.创建项目,导入图片及plist文件
2.创建数据模型,用来进行plist字典数据转模型
3.创建一个继承自UITableViewCell的文件用来设置自定义cell以及数据的重写
4.在控制器中遵守UITableViewDataSourse协议,创建代理对象,实现代理方法等。
给模型增加frame数据
- 所有子控件的frame
- cell的高度
@interface XMGStatus : NSObject
/**** 文字\图片数据 ****/
// .....
/**** frame数据 ****/
/** 头像的frame */
@property (nonatomic, assign) CGRect iconFrame;
// .....
/** cell的高度 */
@property (nonatomic, assign) CGFloat cellHeight;
@end
- 重写模型cellH属性的get方法
- (CGFloat)cellHeight
{
if (_cellHeight == 0) {
// ... 计算所有子控件的frame、cell的高度
}
return _cellHeight;
}
在控制器中
- 实现一个返回cell高度的代理方法
- 在这个方法中返回indexPath位置对应cell的高度
/**
* 返回每一行cell的具体高度
*/
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
XMGStatus *status = self.statuses[indexPath.row];
return status.cellH;
}
- 给cell传递模型数据
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"status";
// 访问缓存池
ZYStatusCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 设置数据(传递模型数据)
cell.status = self.statuses[indexPath.row];
return cell;
}
新建一个继承自UITableViewCell
的子类,比如ZYStatusCell
@interface ZYStatusCell : UITableViewCell
@end
在ZYStatusCell.m文件中
- 重写
-initWithStyle:reuseIdentifier:
方法- 在这个方法中添加所有可能显示的子控件
- 给子控件做一些初始化设置(设置字体、文字颜色等)
/**
* 在这个方法中添加所有的子控件
*/
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
// ......
}
return self;
}
在ZYStatusCell.h文件中提供一个模型属性,比如ZYStatus模型
@class ZYStatus;
@interface XMGStatusCell : UITableViewCell
/** 团购模型数据 */
@property (nonatomic, strong)ZYStatus *status;
@end
在ZYStatusCell.m文件中重写模型属性的set方法
- 在set方法中给子控件设置模型数据
- (void)setStatus:(ZYStatus *)status
{
_status = status;
// .......
}
重写-layoutSubviews
方法
- 一定要调用
[super layoutSubviews]
- 在这个方法中
设置
所有子控件的frame
/**
* 在这个方法中设置所有子控件的frame
*/
- (void)layoutSubviews
{
[super layoutSubviews];
// ......
}
具体代码如下;
ZYStatus文件:
#import <UIKit/UIKit.h>
// 模型数据
@interface ZYStatus : NSObject
/** 图像 */
@property (nonatomic, copy) NSString *icon;
/** 昵称 */
@property (nonatomic, copy) NSString *name;
/** 正文(内容) */
@property (nonatomic, copy) NSString *text;
/** VIP */
@property (nonatomic, assign, getter=isVip) BOOL vip;
/** 配图 */
@property (nonatomic, copy) NSString *picture;
/** 图像的frame */
@property(nonatomic,assign)CGRect iconFrame;
/** 昵称的frame */
@property(nonatomic,assign)CGRect nameFrame;
/** vip的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
@implementation ZYStatus
- (CGFloat)cellHeight
{
if (_cellHeight == 0) {
// 设置子控件的frame
CGFloat space = 10;
/** 图像 */
CGFloat iconX = space;
CGFloat iconY = space;
CGFloat iconWH = 30;
self.iconFrame = CGRectMake(iconX, iconY, iconWH, iconWH);
/** 昵称 */
CGFloat nameX = CGRectGetMaxX(self.iconFrame) + space;
CGFloat nameY = iconY;
NSDictionary *nameAtt = @{NSFontAttributeName : [UIFont systemFontOfSize:16]};
//计算昵称文字的尺寸
CGSize nameSize = [self.name sizeWithAttributes:nameAtt];
CGFloat nameW = nameSize.width;
CGFloat nameH = nameSize.height;
self.nameFrame = CGRectMake(nameX, nameY, nameW, nameH);
/** vip */
if (self.isVip) {
CGFloat vipX = CGRectGetMaxX(self.nameFrame) + space;
// CGFloat vipWH = 14;
// CGFloat vipY = nameY + (nameH - vipWH) * 0.5;
CGFloat vipW = 14;
CGFloat vipH = nameH;
CGFloat vipY = nameY;
self.vipFrame = CGRectMake(vipX, vipY, vipW, vipH);
}
/** 正文 */
CGFloat textX = iconX;
CGFloat textY = CGRectGetMaxY(self.iconFrame) + space;
CGFloat textW = [UIScreen mainScreen].bounds.size.width - 2 *space;
NSDictionary *textAtt = @{NSFontAttributeName : [UIFont systemFontOfSize:14]};
// 计算文字的高度,需要知道文字从什么位置开始换行,且文字字数不限定(高度不限制)
CGSize textSize = CGSizeMake(textW, MAXFLOAT);
// CGFloat textH = [self.status.text sizeWithFont:ZYTextFont constrainedToSize:textSize].height;
CGFloat textH = [self.text boundingRectWithSize:textSize options:NSStringDrawingUsesLineFragmentOrigin attributes:textAtt context:nil].size.height;
self.textFrame = CGRectMake(textX, textY, textW, textH);
/** 配图 */
if (self.picture) {
CGFloat pictureWH = 100;
CGFloat pictureX = iconX;
CGFloat pictureY = CGRectGetMaxY(self.textFrame) + space;
self.pictureFrame = CGRectMake(pictureX, pictureY, pictureWH, pictureWH);
_cellHeight = CGRectGetMaxY(self.pictureFrame) + space;
}else{
_cellHeight = CGRectGetMaxY(self.textFrame) + space;
}
}
return _cellHeight;
}
@end
ZYStatusCell文件
#import <UIKit/UIKit.h>
@class ZYStatus;
@interface ZYStatusCell : UITableViewCell
/** 微博模型数据 */
@property(nonatomic,strong)ZYStatus *status;
@end
#import "ZYStatus.h"
#define ZYTextFont [UIFont systemFontOfSize:14]
#define ZYNameFont [UIFont systemFontOfSize:16]
@interface ZYStatusCell ()
/** 图像 */
@property (nonatomic, weak) UIImageView *iconImageView;
/** 昵称 */
@property (nonatomic, weak) UILabel *nameLabel;
/** vip */
@property (nonatomic, weak) UIImageView *vipImageView;
/** 正文 */
@property (nonatomic, weak) UILabel *text_Label;
/** 配图 */
@property (nonatomic, weak) UIImageView *pictureImageView;
@end
@implementation ZYStatusCell
//1. 添加子控件的原则:把所有有可能显示的子控件都先添加进去
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
// 添加子控件
/** 图像 */
UIImageView *iconImageView = [[UIImageView alloc] init];
[self.contentView addSubview:iconImageView];
self.iconImageView = iconImageView;
/** 配图 */
UIImageView *pictureImageView = [[UIImageView alloc] init];
[self.contentView addSubview:pictureImageView];
self.pictureImageView = pictureImageView;
/** vip */
UIImageView *vipImageView = [[UIImageView alloc] init];
// 设置vip图片的内容模式
vipImageView.contentMode = UIViewContentModeCenter;
[self.contentView addSubview:vipImageView];
self.vipImageView = vipImageView;
/** 昵称 */
UILabel *nameLabel = [[UILabel alloc] init];
// 文字的大小和下面保持一致
nameLabel.font = ZYNameFont;
[self.contentView addSubview:nameLabel];
self.nameLabel = nameLabel;
/** 正文 */
UILabel *text_Label = [[UILabel alloc] init];
text_Label.numberOfLines = 0;
text_Label.font = ZYTextFont;
[self.contentView addSubview:text_Label];
self.text_Label = text_Label;
}
return self;
}
//2. 给子控件设置frame
- (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;
}
//3. 给子控件赋值
- (void)setStatus:(ZYStatus *)status
{
_status = status;
// 赋值
self.iconImageView.image = [UIImage imageNamed:status.icon];
self.nameLabel.text = status.name;
if (status.isVip) {
self.vipImageView.hidden = NO;
// 有if就有else,如果在这里写字体颜色,会出现数据紊乱,不是会员的用户的名称也是黄色
self.nameLabel.textColor = [UIColor orangeColor];
self.vipImageView.image = [UIImage imageNamed:@"vip"];
}else{
self.vipImageView.hidden = YES;
self.nameLabel.textColor = [UIColor blackColor];
}
self.text_Label.text = status.text;
if (status.picture) {
self.pictureImageView.hidden = NO;
self.pictureImageView.image = [UIImage imageNamed:status.picture];
}else{
self.pictureImageView.hidden = YES;
}
}
@end
ViewController 文件
#import "ViewController.h"
#import "MJExtension.h"
#import "ZYStatusCell.h"
#import "ZYStatus.h"
@interface ViewController ()
/** 所有的微博 */
@property (nonatomic,strong) NSArray *statuses;
@end
@implementation ViewController
- (NSArray *)statuses
{
if (_statuses == nil) {
//加载plist中的数据到,模型ZYStatus中
_statuses = [ZYStatus mj_objectArrayWithFilename:@"statuses.plist"];
}
return _statuses;
}
NSString *ID = @"status";
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.rowHeight = 250;
// 注册
[self.tableView registerClass:[ZYStatusCell class] forCellReuseIdentifier:ID];
}
#pragma -mark 数据源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.statuses.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 到缓存池中查看是否有可循环利用的cell
ZYStatusCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 传递模型数据
cell.status = self.statuses[indexPath.row];
return cell;
}
#pragma -mark 代理方法
/*
想要计算cell的高度,必须拿到cell中最后一个子控件的Y值,加上间距,就可以算出cell的高度
想拿到最后一个子控件就必须拿到cell.通过cell.拿到cell最后一个子控件的Y值+间距.就求出cell的高度了
*/
// 在这个方法返回之前就要计算cell的高度.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 方式三: 通过数据模型来计算cell中子控件的高度
// 通过模型为纽带,把所有子控件的frame,保存起来.在layoutsubviews中使用
// 由于此代理方法调用频繁.每调用一次这个方法都要计算一次子控件的frame,性能不好.
ZYStatus *status = self.statuses[indexPath.row];
return status.cellHeight;
}
@end
根据文字来创建自适应大小方式。
// 自适应文字及高度显示
CGFloat textX = iconX;
CGFloat textY = CGRectGetMaxY(iconImageView) + margin;
// 设置文字的自适应宽度 ,屏幕的宽度减去两边的间距
CGFloat textW = [UIScreen mainScreen].bounds.size.width - iconX * 2;
// 设置文字显示的最大高度
CGSize textMaxSize = CGSizeMake(textW, MAXFLOAT);
// 字典包装文字本身大小
NSDictionary *textSize = @{NSFontAttributeName:[UIFont systemFontOfSize:16.0]
};
// 算出文字的自适应高度
CGFloat textH = [status.text boundingRectWithSize:textMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:textSize context:nil].size.height;
CGRect TEXTLabel = CGRectMake(textX, textY, textW, textH);
// 自适应文字宽度显示
CGFloat nameX = CGRectGetMaxX(self.iconImageView.frame) + margin;
CGFloat nameY = iconY;
// 计算文字本身所占据的尺寸
NSDictionary *nameAttri = @{NSFontAttributeName :[UIFont systemFontOfSize:14.0]
};
CGSize nameSize = [self.status.name sizeWithAttributes:nameAttri];
// 包装成结构体
self.nameLabel.frame = (CGRect){{nameX,nameY},nameSize};