UITableViewCell
- 父类是UIView
- UITableView的每一行都是一个UITableViewCell
UITableViewCell的类型
- 按创建来源区分:
- 系统cell
- 自定义cell
- 按高度区分
- 等高的cell:所有cell的高度是一样的
- 不等高的cell:每一个cell的高度并非都一样
- 按加载来区分
- 动态cell:cell的内容在运行时才能确定
- 静态cell:cell的内容一开始已经确定
- 按是否分组区分
- plain:不分组(各组cell之间不相隔)
- grouped:分组(各组cell之间明显相隔)
tableViewCell的常见属性
// 设置右边的指示样式
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;
UITableViewCell的contentView
- UITableViewCell内部有个默认的子视图:
contentView
,是一个纯粹的view,作用是用于存放所有需要显示子控件,方便管理
- UITableViewCell的contentView定制了下述3个子控件用于显示
- textLabel
- detailTextLabel
- imageView
- UITableViewCell的
UITableViewCellStyle
属性,用于决定使用contentView的哪些子视图显示,以及这些子视图在contentView中的位置- 初始化时设置,一旦设置,修改无效
UITableViewCell的辅助指示视图
- 辅助指示视图的作用是显示一个表示动作的图标,可以通过UITableViewCell的
accessoryType
属性设置
序号 | 属性 | 含义 |
---|---|---|
1 | UITableViewCellAccessoryDisclosureIndicator | 箭头 |
2 | UITableViewCellAccessoryDetailButton | 帮助按钮 |
3 | UITableViewCellAccessoryCheckmark | 打钩 |
4 | UITableViewCellAccessoryDetailDisclosureButton | 帮助按钮 + 箭头 |
5 | UITableViewCellAccessoryNone | 不显示辅助指示视图;默认
|
- 也可以通过cell的
accessoryView
属性来自定义辅助指示视图,优先级比accessoryType
cell.accessoryView = button; // 优先级高于accessoryType,要让accessoryType中的类型显示,要清空accessoryView,设置对应的accessoryType cell.accessoryView = nil; cell.accessoryType = UITableViewCellAccessoryDetailButton;
UITableViewCell的重用原理
- iOS设备的内存有限,如果用UITableView显示成千上万条数据,就需要成千上万个UITableViewCell对象的话,那将会耗尽iOS设备的内存。
重用原理:当滚动列表时,部分UITableViewCell会移出窗口,UITableView会将窗口外的UITableViewCell放入一个对象池中,等待重用。当UITableView要求dataSource返回UITableViewCell时,dataSource会先查看这个对象池,如果池中有未使用的UITableViewCell,dataSource会用新的数据配置这个UITableViewCell,然后返回给UITableView,重新显示到窗口中,从而避免创建新对象
还有一个非常重要的问题:有时候需要自定义UITableViewCell(用一个子类继承UITableViewCell),而且每一行用的不一定是同一种UITableViewCell,所以一个UITableView可能拥有不同类型的UITableViewCell,对象池中也会有很多不同类型的UITableViewCell,那么UITableView在重用UITableViewCell时可能会得到错误类型的UITableViewCell
解决方案:UITableViewCell有个NSString *reuseIdentifier属性,可以在初始化UITableViewCell的时候传入一个特定的字符串标识来设置reuseIdentifier(一般用UITableViewCell的类名)。当UITableView要求dataSource返回UITableViewCell时,先通过一个字符串标识到对象池中查找对应类型的UITableViewCell对象,如果有,就重用,如果没有,就传入这个字符串标识来初始化一个UITableViewCell对象
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1.定义一个cell的标识,注意用static修饰
static NSString *ID = @"czcell";
// 2.从缓存池中取出cell
// reuseIdentifier是只读属性。必须在创建时通过传参的方法设置
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 3.如果缓存池中没有cell
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}
// 4.设置数据
cell.textLabel.text = [NSString stringWithFormat:@"%zd行的数据", indexPath.row];
return cell;
}
UITableViewCell创建的底层方法调用
- 通过init方法或注册类方式创建,底层调用:
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier;
- 通过storyboard和xib创建,底层调用:
// 第一步: - (instancetype)initWithCoder:(NSCoder *)aDecoder; // 第二步: - (id)awakeAfterUsingCoder:(NSCoder *)aDecoder; // 最后一步:初始化子控件在此操作 - (void)awakeFromNib;
代码实现不等高cell -> 通过frame计算及布局控件
-
新建一个继承自
UITableViewCell
的子类,比如XXXCell@interface XXXCell : UITableViewCell @end
-
在XXXCell.m文件中添加子控件并初始化
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { // 添加子控件代码(建议封装成一个方法) // 初始化子控件(建议封装成一个方法,方法名为setup) [self setup]; } return self; } - (void)setup { // 初始化子控件属性 self.textLabel.font = [UIFont systemFontOfSize:18]; self.textLabel.textColor = [UIColor whiteColor]; self.textLabellabel.numberOfLines = 0; // 最好设置这个属性,不等高cell的主要原因是由于label的高度随内容而变化,设置了自动换行,后面计算就能合理算出label的高度,从而使cell自动适配 // 其他子控件属性 }
-
在XXXCell.h文件中提供一个模型属性,比如XXXModel
@class XXXModel; @interface XXXCell : UITableViewCell /** 模型数据 */ @property (nonatomic, strong) XXXModel *model; @end
-
在模型.h文件中设置模型数据,并增加所有子控件的frame数据及cell的高度属性
@interface XXXStatus : NSObject /**** 模型数据 ****/ /** 名字 */ @property (nonatomic ,strong) NSString *name; /** 图片 */ @property (nonatomic ,strong) NSString *icon; // 其他模型属性 /**** 所有子控件的frame数据 ****/ /** 头像的frame */ @property (nonatomic, assign) CGRect iconFrame; // 其他子控件的frame /** cell的高度 */ @property (nonatomic, assign) CGFloat cellHeight; @end
-
在模型.m文件中重写模型cellHeight属性的get方法(核心),主要是label的包裹效果
- (CGFloat)cellHeight { // 必须有这个判断,否则每次刷新cell都会不断重算cellHeight if (_cellHeight == 0) { // ... 计算所有子控件的frame、cell的高度,并记录下来 // 尺寸不变的控件frame计算,例如图片 CGFloat iconX = margin; CGFloat iconY = margin; CGFloat iconW = 50; CGFloat iconH = 50; self.iconFrame =CGRectMake(iconX, iconY, iconW, iconH);// 记录控件大小 // 尺寸变化的控件frame计算,例如label(随文字内容多寡变化) // 计算方式一:文本虽然变化,但不超过1行 // 文本控件名:nameLabel;图片控件名:iconImageView CGFloat nameX = CGRectGetMaxX(self.iconImageViewFrame) + margin; CGFloat nameY = iconY ; NSDictionary *nameAttributes = @{NSFontAttributeName : [UIFont systemFontOfSize:17]}; CGSize nameSize = [self.name sizeWithAttributes:nameAttributes]; self.nameLabelFrame =CGRectMake(nameX, nameY, nameSize.width, nameSize.height); // 计算方式二:文本不断变化,且可能超过1行 // nameLabel,最好在初始化时设置 // nameLabel.numberOfLines = 0; CGFloat nameX = CGRectGetMaxX(self.iconImageViewFrame) + margin; CGFloat nameY = CGRectGetMaxY(self.iconImageViewFrame) + margin; CGFloat nameW = [UIScreen mainScreen].bounds.size.width - 2 * nameX; NSDictionary *nameAttributes = @{NSFontAttributeName : [UIFont systemFontOfSize:14]}; CGSize nameMaxSize = CGSizeMake(TEXTW, MAXFLOAT); CGFloat nameH = [self.text boundingRectWithSize:TEXTMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:nameAttributes context:nil].size.height; self.nameLabelFrame =CGRectMake(nameX, nameY, nameW, nameH); // cellHeight等于最后一个控件的最大Y值 + 间隔值 // _cellHeight = ... } return _cellHeight; }
-
在XXXCell.m中重写模型属性的set方法
- (void)setModel:(XXXModel *)model { _model = model; // 给子控件设置模型数据 self.imageView.image = [UIImage imageNamed:self.model.icon]; // ....... }
-
在XXXCell.m中重写
-layoutSubviews
方法,布局子控件/** * 在这个方法中设置所有子控件的frame */ - (void)layoutSubviews { // 一定要调用`[super layoutSubviews]` [super layoutSubviews]; // 布局子控件的代码,根据模型中计算出的frame设置控件frame self.imageView.frame = self.model.iconFrame; // ... }
-
在控制器中,添加模型数组属性,并将数据源(字典数组)转化为模型数组
#pragma mark - lzay load - (NSMutableArray *)models { if (!_models) { // 获取字典数组 // 字典数组转模型数组代码 } return _models; }
-
在控制器中,注册cell的类型(可选,不注册,则通过alloc init方法创建)
[self.tableView registerClass:[XXXTgCell class] forCellReuseIdentifier:ID];
-
在控制器中,实现数据源方法
// 多少行cell -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.models.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString * const ID = @"model";// Identifier一般用cell的类名 // 访问缓存池 XXXCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; // 创建cell,若第8步注册了,此处可省略 // if (!cell) { cell = [[XXXCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier:ID]; } // 设置数据(传递模型数据) cell.model = self.models[indexPath.row]; return cell; }
-
在控制器中,实现一个返回cell高度的代理方法
// 返回indexPath位置对应cell的高度 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { XXXModel *model = self.models[indexPath.row]; return model.cellHeight; }
代码实现不等高cell -> 通过约束布局控件
-
同“代码实现不等高cell -> 通过frame计算及布局控件”第1步(新建一个继承自
UITableViewCell
的子类)@interface XXXCell : UITableViewCell @end
-
在“代码实现不等高cell -> 通过frame计算及布局控件”第2步(添加子控件并初始化)的基础上:
-
添加子控件约束
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { // 添加子控件代码(建议封装成一个方法) // 初始化子控件(建议封装成一个方法,方法名为setup) [self setup]; // 添加子控件约束(建议封装成一个方法) // 不是在layoutSubview中设置,因为只需要设置一次,而layoutSubview是系统自动调用,调用次数不确定 } return self; } - (void)setup { // 初始化子控件属性 self.textLabel.font = [UIFont systemFontOfSize:18]; self.textLabel.textColor = [UIColor whiteColor]; self.textLabellabel.numberOfLines = 0; // 最好设置这个属性,不等高cell的主要原因是由于label的高度随内容而变化,设置自动换行 // 其他子控件属性 }
-
-
同“代码实现不等高cell -> 通过frame计算及布局控件”第3步(提供模型属性)
@class XXXModel; @interface XXXCell : UITableViewCell /** 模型数据 */ @property (nonatomic, strong) XXXModel *model; @end
-
大致同“代码实现不等高cell -> 通过frame计算及布局控件”第4步(添加模型属性),但不需要计算及记录子控件的frame数据及cell的高度属性
@interface XXXStatus : NSObject /**** 模型属性 ****/ /** 名字 */ @property (nonatomic ,strong) NSString *name; /** 图片 */ @property (nonatomic ,strong) NSString *icon; // 其他模型属性
不需要“代码实现不等高cell -> 通过frame计算及布局控件”第5步(计算cellHeigth),因为约束会自动处理控件高度
-
同“代码实现不等高cell -> 通过frame计算及布局控件”第6步(重写模型属性的set方法,给子控件设置模型数据)
- (void)setModel:(XXXModel *)model { _model = model; // 给子控件设置模型数据 self.imageView.image = [UIImage imageNamed:self.model.icon]; // ....... }
不需要“代码实现不等高cell -> 通过frame计算及布局控件”第7步(在layoutSubviews布局子控件)
-
同“代码实现不等高cell -> 通过frame计算及布局控件”第8步(在控制器中,添加模型数组属性,并将数据源(字典数组)转化为模型数组)
#pragma mark - lzay load - (NSMutableArray *)models { if (!_models) { // 获取字典数组 // 字典数组转模型数组代码 } return _models; }
-
同“代码实现不等高cell -> 通过frame计算及布局控件”第9步(注册cell的类型)(此步可选)
[self.tableView registerClass:[XXXTgCell class] forCellReuseIdentifier:ID];
-
同“代码实现不等高cell -> 通过frame计算及布局控件”第10步(在控制器中,实现数据源方法)
// 多少行cell -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.models.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString * const ID = @"model";// Identifier一般用cell的类名 // 访问缓存池 XXXCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; // 创建cell,若第8步注册了,此处可省略 // if (!cell) { cell = [[XXXCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier:ID]; } // 设置数据(传递模型数据) cell.model = self.models[indexPath.row]; return cell; }
不需要“代码实现不等高cell -> 通过frame计算及布局控件”第11步(告诉tableView每行cell的高度)
通过storyBoard/xib实现UITableViewCell布局
通过storyBoard实现等高
cell
-
新建一个继承自
UITableViewCell
的子类,比如XXXCell@interface XXXCell : UITableViewCell @end
-
在storyboard文件中,找到UITableView里面的cell(动态cell)
-
修改cell的class为XXXCell
-
-
绑定循环利用标识
-
添加子控件,设置子控件约束
-
将子控件连线到类扩展中
@interface XXXCell() @property (weak, nonatomic) IBOutlet UIImageView *iconImageView; @property (weak, nonatomic) IBOutlet UILabel *titleLabel; @property (weak, nonatomic) IBOutlet UILabel *priceLabel; @property (weak, nonatomic) IBOutlet UILabel *buyCountLabel; @end
-
在XXXCell.h文件中提供一个模型属性,比如XXXModel
@class XXXModel; @interface XXXCell : UITableViewCell /** 团购模型数据 */ @property (nonatomic, strong) XXXModel *model; @end
-
在XXXCell.m中重写模型属性的set方法
- (void)setModel:(XXXModel *)model { _model = model; // 给子控件设置模型数据 self.imageView.image = [UIImage imageNamed:self.model.icon]; // 其他子控件属性设置 }
-
在控制器中,添加模型数组属性,并将数据源(字典数组)转化为模型数组
#pragma mark - lzay load - (NSMutableArray *)models { if (!_models) { // 获取字典数组 // 字典数组转模型数组代码 } return _models; }
-
在控制器中,实现数据源方法(不需要注册cell)
// 多少行cell -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.models.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString * const ID = @"model";// Identifier一般用cell的类名 // 访问缓存池 XXXCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; // 设置数据(传递模型数据) cell.model = self.models[indexPath.row]; return cell; }
通过xib实现等高
cell
-
新建一个继承自
UITableViewCell
的子类,比如XXXTgCell@interface XXXTgCell : UITableViewCell @end
-
新建一个xib文件(文件名最好跟类名一致,比如XXXCell.xib)
-
修改cell的class为XXXCell
-
-
绑定循环利用标识
-
添加子控件,设置子控件约束
-
将子控件连线到类扩展中
@interface XXXCell() @property (weak, nonatomic) IBOutlet UIImageView *iconImageView; @property (weak, nonatomic) IBOutlet UILabel *titleLabel; @property (weak, nonatomic) IBOutlet UILabel *priceLabel; @property (weak, nonatomic) IBOutlet UILabel *buyCountLabel; @end
-
在XXXCell.h文件中提供一个模型属性,比如XXXModel
@class XXXModel; @interface XXXCell : UITableViewCell /** 团购模型数据 */ @property (nonatomic, strong) XXXModel *model; @end
-
在XXXCell.m中重写模型属性的set方法
- (void)setModel:(XXXModel *)model { _model = model; // 给子控件设置模型数据 self.imageView.image = [UIImage imageNamed:self.model.icon]; // 其他子控件属性设置 }
-
在控制器中,添加模型数组属性,并将数据源(字典数组)转化为模型数组
#pragma mark - lzay load - (NSMutableArray *)models { if (!_models) { // 获取字典数组 // 字典数组转模型数组代码 } return _models; }
-
在控制器中,注册xib文件(必须)
[self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass([XXXCell class]) bundle:nil] forCellReuseIdentifier:ID];
-
在控制器中,实现数据源方法
// 多少行cell -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.models.count; } // 需要将ID定义抽出来 static NSString * const ID = @"model";// ID一般用cell的类名 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // 访问缓存池 XXXCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; // 设置数据(传递模型数据) cell.model = self.models[indexPath.row]; return cell; }
通过storyBoard/xib实现不等高
cell
对比通过storyBoard/xib实现等高cell,需要几个额外的步骤
- IOS8之后
-
在实现等高cell的第4步,还需要添加子控件和contentView之间的间距约束
-
2. 在控制器的viewDidLoad中设置tableViewCell的真实行高和估算行高
```objc
// 告诉tableView所有cell的真实高度是自动计算(根据设置的约束来计算)
self.tableView.rowHeight = UITableViewAutomaticDimension;
// 告诉tableView所有cell的估算高度
self.tableView.estimatedRowHeight = 44;
```
- iOS8之前
-
如果cell内部有自动换行的label,需要设置preferredMaxLayoutWidth属性
- (void)awakeFromNib { // 手动设置文字的最大宽度(目的是:让label知道自己文字的最大宽度,进而能够计算出自己的frame) self.text_label.preferredMaxLayoutWidth = [UIScreen mainScreen].bounds.size.width - 20; }
-
设置tableView的cell估算高度
// 告诉tableView所有cell的估算高度(设置了估算高度,就可以减少tableView:heightForRowAtIndexPath:方法的调用次数) self.tableView.estimatedRowHeight = 200; // IOS7启用estimatedRowHeight
-
在代理方法中计算cell的高度
XXXCell *cell; - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { // 创建一个cell(cell的作用:根据模型数据布局所有的子控件,进而计算出cell的高度) if (!cell) { cell = [tableView dequeueReusableCellWithIdentifier:ID]; } // 设置模型数据 cell.model = self.models[indexPath.row]; return cell.height; } // 在XXXCell.h中添加height属性,在.m文件中实现计算 - (CGFloat)height { // 强制布局cell内部的所有子控件(label根据文字多少计算出自己最真实的尺寸) [self layoutIfNeeded]; // 计算cell的高度 // 这里的场景是有些cell有大图片,有些cell没有大图片,cell的高度需要根据情况判断 if (self.model.picture) { return CGRectGetMaxY(self.pictureImageView.frame) + 10; } else { return CGRectGetMaxY(self.text_label.frame) + 10; } }
-