第一种方法 :使用代码创建
分析:
(1)重写initWithStyle
方法,在这个方法里创建控件或者尺寸约束(其实控件约束一般写在layoutSubviews
里)。
(2)在layoutSubviews
方法里设置尺寸
(3)重写模型的set方法
重写模型的set方法:
先声明模型类型,接受模型数据
@class XMGTopic;
@interface XMGTopicCell : UITableViewCell
/** 模型数据 */
@property (nonatomic, strong) XMGTopic *topic;
@end
代码:
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
// 增加顶部的控件,并且设置约束
// ...
// 增加底部的控件,并且设置约束
// ...
}
return self;
}
/*
- (void)layoutSubviews
{
[super layoutSubviews];
// 设置顶部和底部控件的frame
}*/
- (void)setTopic:(XMGTopic *)topic
{
_topic = topic;
// 设置顶部和底部控件的具体数据(比如文字数据、图片数据)
}
@end
在Controller里创建cell并导入数据也有2种方法:
第一种:不常用(最基本的创建cell方法)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"cell";
// 缓存池去取
XMGTopicCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) { // 缓存池没有,创建
cell = [[XMGTopicCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
}
// 导入数据
cell.topic = self.topics[indexPath.row];
return cell;
}
第二种:通过注册,常用
注册的功能是:去缓存池取cell,若没有创建cell
/* cell的重用标识 */
static NSString * const XMGTopicCellId = @"XMGTopicCellId";
// 注册cell, registerClass:代码注册,registerNib:xib注册
[self.tableView registerClass:[XMGTopicCell class] forCellReuseIdentifier:XMGTopicCellId];
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
XMGTopicCell *cell = [tableView dequeueReusableCellWithIdentifier:XMGTopicCellId];
// 导入数据
cell.topic = self.topics[indexPath.row];
return cell;
}
第二种方法:使用xib创建
分析:
(1)在- (void)awakeFromNib
中进行控件设置
(2)重写模型的set方法
代码:
- (void)awakeFromNib
{
self.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"mainCellBackground"]];
}
- (void)setTopic:(XMGTopic *)topic
{
_topic = topic;
// 顶部控件的数据
[self.profileImageView sd_setImageWithURL:[NSURL URLWithString:topic.profile_image] placeholderImage:[UIImage imageNamed:@"defaultUserIcon"]];
self.nameLabel.text = topic.name;
self.passtimeLabel.text = topic.passtime;
self.text_label.text = topic.text;
// 底部按钮的文字
[self setupButtonTitle:self.dingButton number:topic.ding placeholder:@"顶"];
[self setupButtonTitle:self.caiButton number:topic.cai placeholder:@"踩"];
[self setupButtonTitle:self.repostButton number:topic.repost placeholder:@"分享"];
[self setupButtonTitle:self.commentButton number:topic.comment placeholder:@"评论"];
}
在Controller里注册cell并导入数据2种方法
第一种:通过mainBundle
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"cell";
// 缓存池去取
XMGTopicCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) { // 缓存池没有,创建
cell = [[[NSBundle mainBundle] loadNibNamed:self owner:nil options:nil] firstObject];
}
// 导入数据
cell.topic = self.topics[indexPath.row];
return cell;
}
第二种:通过注册加载cell
/* cell的重用标识 */
static NSString * const XMGTopicCellId = @"XMGTopicCellId";
// 注册cell
UINib *nib = [UINib nibWithNibName:NSStringFromClass([XMGTopicCell class]) bundle:nil];
[self.tableView registerNib:nib forCellReuseIdentifier:XMGTopicCellId];
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
XMGTopicCell *cell = [tableView dequeueReusableCellWithIdentifier:XMGTopicCellId];
cell.topic = self.topics[indexPath.row];
return cell;
}
技巧:因为通过mainBundle加载资源,使用地方很多,可以将其封装在UIView的分类里
代码:
+ (instancetype)xmg_viewFromXib;
+ (instancetype)xmg_viewFromXib
{
return [[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self) owner:nil options:nil].firstObject;
}
拓展:若有四种cell,其中有共同的部分,也有各自不同的部分,如何做?
分析:2种思路
第一种思路:父cell+子cell
分析:
- 因为xib无法继承,所以需要用纯代码或者顶部一个xib,底部一个xib,添加到cell上
- 在自定义子cell时,其它都相同,就是
-setTopic
时,需要先调用一下父控件的-setTopic
方法,即将共同的顶部与底部先加载进来并设置好数据。
子cell的自定义代码如下:
@implementation XMGVoiceCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
// 增加中间的声音控件,并且设置约束
// ...
self.backgroundColor = [UIColor greenColor];
}
return self;
}
/*
- (void)layoutSubviews
{
[super layoutSubviews];
// 设置中间声音控件的frame
}*/
- (void)setTopic:(XMGTopic *)topic
{
[super setTopic:topic];
// 设置中间声音控件的具体数据(比如文字数据、图片数据)
}
@end
第二种思路:只用一种cell
分析:
**(1)创建整体cell;
(2)中间内容: 自定义三种xib,分别是视频、语音、图片
(3)中间内容懒加载进整体cell中
**
@class XMGTopic;
@interface XMGTopicCell : UITableViewCell
/** 模型数据 */
@property (nonatomic, strong) XMGTopic *topic;
/* 中间控件 */
/** 图片控件 */
@property (nonatomic, weak) XMGTopicPictureView *pictureView;
/** 声音控件 */
@property (nonatomic, weak) XMGTopicVoiceView *voiceView;
/** 视频控件 */
@property (nonatomic, weak) XMGTopicVideoView *videoView;
@end
@implementation XMGTopicCell
#pragma mark - 懒加载
- (XMGTopicPictureView *)pictureView
{
if (!_pictureView) {
XMGTopicPictureView *pictureView = [XMGTopicPictureView xmg_viewFromXib];
[self.contentView addSubview:pictureView];
_pictureView = pictureView;
}
return _pictureView;
}
- (XMGTopicVoiceView *)voiceView
{
if (!_voiceView) {
XMGTopicVoiceView *voiceView = [XMGTopicVoiceView xmg_viewFromXib];
[self.contentView addSubview:voiceView];
_voiceView = voiceView;
}
return _voiceView;
}
- (XMGTopicVideoView *)videoView
{
if (!_videoView) {
XMGTopicVideoView *videoView = [XMGTopicVideoView xmg_viewFromXib];
[self.contentView addSubview:videoView];
_videoView = videoView;
}
return _videoView;
}
#pragma mark - 初始化
- (void)awakeFromNib
{
self.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"mainCellBackground"]];
}
- (void)setTopic:(XMGTopic *)topic
{
_topic = topic;
// 顶部控件的数据
UIImage *placeholder = [UIImage xmg_circleImageNamed:@"defaultUserIcon"];
[self.profileImageView sd_setImageWithURL:[NSURL URLWithString:topic.profile_image] placeholderImage:placeholder options:0 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
// 图片下载失败,直接返回,按照它的默认做法
if (!image) return;
self.profileImageView.image = [image xmg_circleImage];
}];
self.nameLabel.text = topic.name;
self.passtimeLabel.text = topic.passtime;
self.text_label.text = topic.text;
// 底部按钮的文字
[self setupButtonTitle:self.dingButton number:topic.ding placeholder:@"顶"];
[self setupButtonTitle:self.caiButton number:topic.cai placeholder:@"踩"];
[self setupButtonTitle:self.repostButton number:topic.repost placeholder:@"分享"];
[self setupButtonTitle:self.commentButton number:topic.comment placeholder:@"评论"];
// 最热评论
if (topic.top_cmt.count) { // 有最热评论
self.topCmtView.hidden = NO;
NSDictionary *cmt = topic.top_cmt.firstObject;
NSString *content = cmt[@"content"];
if (content.length == 0) { // 语音评论
content = @"[语音评论]";
}
NSString *username = cmt[@"user"][@"username"];
self.topCmtLabel.text = [NSString stringWithFormat:@"%@ : %@", username, content];
} else { // 没有最热评论
self.topCmtView.hidden = YES;
}
// 中间的内容
if (topic.type == XMGTopicTypePicture) { // 图片
self.pictureView.hidden = NO;
self.voiceView.hidden = YES;
self.videoView.hidden = YES;
} else if (topic.type == XMGTopicTypeVoice) { // 声音
self.pictureView.hidden = YES;
self.voiceView.hidden = NO;
self.voiceView.topic = topic;
self.videoView.hidden = YES;
} else if (topic.type == XMGTopicTypeVideo) { // 视频
self.pictureView.hidden = YES;
self.voiceView.hidden = YES;
self.videoView.hidden = NO;
} else if (topic.type == XMGTopicTypeWord) { // 段子
self.pictureView.hidden = YES;
self.voiceView.hidden = YES;
self.videoView.hidden = YES;
}
}
- (void)layoutSubviews
{
[super layoutSubviews];
if (self.topic.type == XMGTopicTypePicture) { // 图片
self.pictureView.frame = self.topic.middleFrame;
} else if (self.topic.type == XMGTopicTypeVoice) { // 声音
self.voiceView.frame = self.topic.middleFrame;
} else if (self.topic.type == XMGTopicTypeVideo) { // 视频
self.videoView.frame = self.topic.middleFrame;
}
}
问题1:如何避免同一个cell的高度计算多次,即让同一个cel的高度只计算一次?
答案1:利用模型属性,额外增加计算cell高度的属性,重写cellHeight
的getter方法。在cellHeight
方法里判断是否计算过行高,已经计算过就返回)
流程分析:
当UITableView显示的时候,系统开始就会调用
numberOfSections
和numberOfRows
方法,来知道行数和组数然后调用
heightForRow
方法,计算所有cell的高度,计算出来tableView的contentsize,确定tableView的滑动范围。再调用
cellForRow
方法,显示出cell当屏幕来回滑动的时候,
heightForRow
方法会调用的频繁解决上述问题,就定义一个模型属性,重写
cellHeight
的getter方法,已经计算过就返回
拓展:
heightForRow
这个方法的特点:
- 默认情况下,每次刷新表格的时候,有多少数据,这个方法就一次调用多少次 ---如:若有100条数据,每次
reloadData
,这个方法就会调用100次。 - 每当有cell进入屏幕范围,就会调用一次这个方法。
/* 额外增加的属性(并非服务器返回的属性,仅仅是为了提高开发效率) */
/** 根据当前模型计算出来的cell高度 */
@property (nonatomic, assign) CGFloat cellHeight;
/** 中间内容的frame */
@property (nonatomic, assign) CGRect middleFrame;
#import "XMGTopic.h"
@implementation XMGTopic
- (CGFloat)cellHeight
{
// 如果已经计算过,就直接返回
if (_cellHeight) return _cellHeight;
// 文字的Y值
_cellHeight += 55;
// 文字的高度
CGSize textMaxSize = CGSizeMake(XMGScreenW - 2 * XMGMarin, MAXFLOAT);
_cellHeight += [self.text boundingRectWithSize:textMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:15]} context:nil].size.height + XMGMarin;
// 中间的内容
if (self.type != XMGTopicTypeWord) { // 中间有内容(图片、声音、视频)
CGFloat middleW = textMaxSize.width;
CGFloat middleH = middleW * self.height / self.width;
CGFloat middleY = _cellHeight;
CGFloat middleX = XMGMarin;
self.middleFrame = CGRectMake(middleX, middleY, middleW, middleH);
_cellHeight += middleH + XMGMarin;
}
// 最热评论
if (self.top_cmt.count) { // 有最热评论
// 标题
_cellHeight += 21;
// 内容
NSDictionary *cmt = self.top_cmt.firstObject;
NSString *content = cmt[@"content"];
if (content.length == 0) {
content = @"[语音评论]";
}
NSString *username = cmt[@"user"][@"username"];
NSString *cmtText = [NSString stringWithFormat:@"%@ : %@", username, content];
_cellHeight += [cmtText boundingRectWithSize:textMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:16]} context:nil].size.height + XMGMarin;
}
// 工具条
_cellHeight += 35 + XMGMarin;
return _cellHeight;
}
@end
在Controller,返回每行模型对应的行高(在模型里,如果已经计算好,就会返回,若没有计算行高,就会继续执行计算行高)
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
XMGTopic * topic=self.topics[indexPath.row];
return topic.cellHeight;
}