UITableViewCell
- cell内部的控件是懒加载的,用到时才创建并且只创建一次
UITableViewCell的常见设置
// 设置右边的指示样式
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
// 设置右边的指示控件
cell.accessoryView = [[UISwitch alloc] init];
// 设置cell的选中样式
cell.selectionStyle = UITableViewCellSelectionStyleNone;
// backgroundView优先级 > backgroundColor
// 设置背景色
cell.backgroundColor = [UIColor redColor];
// 设置背景view
UIView *bg = [[UIView alloc] init];
bg.backgroundColor = [UIColor blueColor];
cell.backgroundView = bg;
// 设置选中的背景view
UIView *selectedBg = [[UIView alloc] init];
selectedBg.backgroundColor = [UIColor purpleColor];
cell.selectedBackgroundView = selectedBg;
cell的循环利用
- cell的重用原理
重用原理:
1.当滚动列表时,部分Cell被会移出窗口,UITableView会将窗口外的Cell放入缓存池中,等待重用。当UITableView要求dataSource返回UITableViewCell时,dataSource会先查看缓存池,如果池中有未使用的UITableViewCell,dataSource会用新的数据覆盖这个UITableViewCell原有的数值,然后返回给UITableView,重新显示到窗口中,从而避免创建新对象
2.还有一个非常重要的问题:
缓存池中可能有不同类型的cell,为了防止取出的不是我们想要的cell,所以我们需要在初始化cell的时候给cell设置一个特定的字符串来标识,当tableView要求数据源返回cell时,会先通过该标识到缓存池中查找对应类型的cell,如果有就重用,如果没有,就用该标识来初始化一个新的cell
- 传统的写法
/**
* 每当有一个cell要进入视野范围内,就会调用一次
*/
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"wine";
// 1.先去缓存池中查找可循环利用的cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 2.如果缓存池中没有可循环利用的cell
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
}
// 3.设置数据
cell.textLabel.text = [NSString stringWithFormat:@"%zd行的数据", indexPath.row];
return cell;
}
- 新的写法(注册cell)
- 该方式无法设置cell的样式
NSString *ID = @"wine";
- (void)viewDidLoad {
[super viewDidLoad];
// 注册某个重用标识 对应的 Cell类型
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:ID];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1.先去缓存池中查找可循环利用的cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 2.设置数据
cell.textLabel.text = [NSString stringWithFormat:@"%zd行的数据", indexPath.row];
return cell;
}
自定义Cell
自定义等高cell
- 代码自定义cell
1.新建一个继承自UITableViewCell的类
2.添加控件属性和模型属性
3.在layoutSubviews方法中布局子控件
3.1通过frame布局
3.2通过AutoLayout 布局
4.在传入模型的set方法中给子控件属性赋值
5.添加便利构造方法
- xib自定义cell
1. 新建一个继承自UITableViewCell的类
2. 新建xib文件
2.1 在xib文件中布局好子控件
2.2 设置xib的class属性
2.3 设置cell的重用标识
2.4 连线子控件
3. 加载xib文件
4. 在模型属性的set方法中给子控件赋值
- storyboard自定义cell
1. 新建一个继承自UITableViewCell的类
2. 在storyboard中布局好子控件
2.1 设置cell的class属性
2.2 设置cell的重用标识
2.3 连线子控件
3. 通过继承自UITableViewCell的类设置cell里面每个控件的属性
// 3. 也可以直接通过viewWithTag,直接给子控件赋值(不推荐)
自定义非等高cell
- 添加子控件的原则
1. 把有可能显示的控件,都先添加进去,然后根据数据的情况确定这些控件显示还是隐藏(hidden属性)
2. 有YES就要有与之对应的NO , 有if 就有与之对应的else(否则会引发循环引用问题)
- UILabel真实宽高计算
// 单行时计算文本的真实尺寸,返回值CGSize
NSDictionary *atrriDict = @{NSFontAttributeName:[UIFont systemFontOfSize:17]}
[label.text sizeWithAttributes:atrriDict]
// 多行时计算文本的真实尺寸,返回值是CGRect(矩形框)
[label.text boundingRectWithSize:contentMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:17]} context:nil].size
- xib自定义cell
// 1. 在模型中增加一个cellHeight属性,用来存放对应cell的高度
// 2. 在cell的模型属性set方法中,调用layoutIfNeed方法强制布局,然后计算出cellHeight的值
// 3. 在控制器中实现tableView的代理方法估算行高(或者设置tableView.estimatedRowHeight)
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 100;
}
// 4. 在控制器中调用tableView的代理方法返回cell的真实高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
LJMessageModel *model = self.messages[indexPath.row];
return model.cellHeight;
}
// 5注意点:如果cell中有自动换行的label需要设置label每一行的最大宽度(宽度有时候不等于中文字符宽度的整数倍,不设置会有小问题)
// 设置label的宽度等于屏幕宽度减去两倍的间距
label.preferrdMaxLayoutWidh = [UIScreen mainScreen].bounds.size.width - 2 * margin
- storyboard自定义非等高cell
1.设置cell的重用标识
2.直接利用[tableView dequeueReusableCellWithIdentifier:@"wine"];
3.不需要判断cell是否为空,再创建cell,系统会自动帮我们完成这一步
4.如果有自动换行的label必须:
设置numberOflines = 0;
设置label的上下左右约束
5.在控制器的viewDidLoad方法
// 设置cell的估算高度
self.tableView.estimatedRowHeight = 100;
// 告诉控制器cell的高度是自动计算的
self.tableView.rowHeight = UITableViewAutomaticDimension;
- 通过frame模型 + cell数据模型
新建一个frame模型,定义属性保存每个控件的frame,以及一个cell数据模型属性
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@class LJMessageModel;
@interface LJFrameModel : NSObject
// 模型属性
@property (nonatomic, strong) LJMessageModel *model;
@property(nonatomic,assign) CGRect timeFrame;
@property(nonatomic,assign)CGRect iconFrame;
@property(nonatomic,assign)CGRect textFrame;
// cell的真实高度
@property(nonatomic,assign)CGFloat cellHeight;
重写模型属性的set方法,再该方法中通过模型属性的数据计算子控件的frame
#import "LJFrameModel.h"
#import "LJMessageModel.h"
// 屏幕的宽高
#define kScreenSize ([UIScreen mainScreen].bounds.size)
#define Margin 10
@implementation LJFrameModel
- (void)setModel:(LJMessageModel *)model{
_model = model;
// 计算frame timeLabel
CGFloat timeX = 0;
CGFloat timeY = 0;
CGFloat timeW = kScreenSize.width;
CGFloat timeH = 20;
_timeFrame = CGRectMake(timeX, timeY, timeW, timeH);
// 计算头像frame
CGFloat iconX;
CGFloat iconY = CGRectGetMaxY(_timeFrame) + Margin;
CGFloat iconWH = 40;
if (_model.type == MessageTypeMe) {// 自己
iconX = kScreenSize.width - Margin - iconWH;
_iconFrame = CGRectMake(iconX, iconY, iconWH, iconWH);
}else{// 其他
iconX = Margin;
_iconFrame = CGRectMake(iconX, iconY, iconWH, iconWH);
}
// 计算textButton的frame
// 屏幕宽度 - 2个头像的宽度 - 头像左右各2个间距的宽度
CGFloat maxWidth = kScreenSize.width - 2 * iconWH - 4 * Margin;
CGSize maxSize = CGSizeMake(maxWidth, MAXFLOAT);
// 根据文字内容计算真实尺寸
NSDictionary *attribute = @{NSFontAttributeName :[UIFont systemFontOfSize:17]};
CGRect bounds = [_model.text boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attribute context:nil];
CGFloat textW = bounds.size.width;
CGFloat textH = bounds.size.height;
CGFloat textX;
CGFloat textY = iconY;
if (_model.type == MessageTypeMe) {
textX = kScreenSize.width - textW - iconWH -2 * Margin - 40;
_textFrame = CGRectMake(textX, textY, textW, textH);
} else {
textX = CGRectGetMaxX(_iconFrame) + Margin;
_textFrame = CGRectMake(textX, textY, textW, textH);
}
// 增加button的宽高 内边距
_textFrame.size.width += 30;
_textFrame.size.height += 30;
_cellHeight = MAX(CGRectGetMaxY(_textFrame), CGRectGetMaxY(_iconFrame));
}
初始化子控件
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
self.backgroundColor = [UIColor redColor];
// 初始化子控件
[self setupUI];
}
return self;
}
- (void)setupUI {
// 时间
UILabel *timeLable = [[UILabel alloc] init];
self.timeLabel = timeLable;
// 居中显示
timeLable.textAlignment = NSTextAlignmentCenter;
[self.contentView addSubview:timeLable];
// 用户头像
UIImageView *iconImageView = [[UIImageView alloc] init];
self.iconImageView = iconImageView;
[self.contentView addSubview:iconImageView];
// 文本buton
UIButton *textButton = [[UIButton alloc] init];
self.textButton = textButton;
textButton.backgroundColor = [UIColor blueColor];
// 设置文本颜色
[textButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
// 设置文本自动换行
textButton.titleLabel.numberOfLines = 0;
// 设置文本的font
textButton.titleLabel.font = [UIFont systemFontOfSize:17];
// 设置button的内边距, 让内容的显示距离button边界有一段距离
// textButton.contentEdgeInsets = UIEdgeInsetsMake(0, 15, 0, 15);
textButton.titleEdgeInsets = UIEdgeInsetsMake(0, 15, 0, 15);
textButton.imageEdgeInsets = UIEdgeInsetsMake(15, 0,0, 0);
[self.contentView addSubview:textButton];
}
重写frame模型的set方法,在该方法中给子控件赋值,传递frame
- (void)setFrameModel:(LJFrameModel *)frameModel{
_frameModel = frameModel;
// 设置数据
[self setupData];
// 设置frame
[self setupFrame];
}
#pragma mark -
#pragma mark - 设置数据
- (void)setupData {
LJMessageModel *model = _frameModel.model;
// 设置时间
_timeLabel.text = model.time;
// 设置头像
// 判断是我, 还是其他人, 设置背景图片
if (model.type == MessageTypeMe) { // 表示自己
// 用户头像
self.iconImageView.image = [UIImage imageNamed:@"me"];
// 背景图片
[self.textButton setBackgroundImage:[UIImage imageNamed:@"chat_send_nor"] forState:UIControlStateNormal];
} else { // 表示别人
// 用户头像
self.iconImageView.image = [UIImage imageNamed:@"other"];
// 背景图片
[self.textButton setBackgroundImage:[UIImage imageNamed:@"chat_recive_press_pic"] forState:UIControlStateNormal];
}
// 设置文本
[self.textButton setTitle:model.text forState:UIControlStateNormal];
}
#pragma mark -
#pragma mark - 设置frame
- (void)setupFrame {
// 时间frame
if (_frameModel.model.isHideTime) {//隐藏时间
self.timeLabel.hidden = YES;
// 更新frame
_timeLabel.frame = CGRectZero;
// 用户头像frame
CGRect iconFrame = _frameModel.iconFrame;
_iconImageView.frame = CGRectMake(iconFrame.origin.x, iconFrame.origin.y - 20, iconFrame.size.width, iconFrame.size.height);
// 文本frame
CGRect textFrame = _frameModel.textFrame;
_textButton.frame = CGRectMake(textFrame.origin.x, textFrame.origin.y - 20, textFrame.size.width, textFrame.size.height);
self.contentView.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height - 20);
}else{// 显示时间
self.timeLabel.hidden = NO;
_timeLabel.frame = _frameModel.timeFrame;
// 用户头像frame
_iconImageView.frame = _frameModel.iconFrame;
// 文本frame
_textButton.frame = _frameModel.textFrame;
self.contentView.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
}
}