一、纯代码自定义不等高cell
废话不多说,直接来看下面这个例子
先来看下微博的最终效果
首先创建一个继承UITableViewController的控制器
@interface ViewController : UITableViewController
创建一个cell模型
@interface XMGStatusCell : UITableViewCell
再创建微博的数据模型
@interface XMGStatus : NSObject

和前面等高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的高度
因为从头到尾我们都没有用代码设置过高度,那么在哪里设置呢?
方案:在heightForRowAtIndexPath:方法调用之前将所有cell的高度计算清楚
1 /** 2 * 返回每一行cell的具体高度 3 */ 4 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 5 { 6 XMGStatus *status = self.statuses[indexPath.row]; 7 8 CGFloat margin = 10; 9 CGFloat cellHeight = 0; 10 11 // 头像 12 CGFloat iconX = margin; 13 CGFloat iconY = margin; 14 CGFloat iconWH = 30; 15 CGRect iconImageViewFrame = CGRectMake(iconX, iconY, iconWH, iconWH); 16 17 // 文字 18 CGFloat textX = iconX; 19 CGFloat textY = CGRectGetMaxY(iconImageViewFrame) + margin; 20 CGFloat textW = [UIScreen mainScreen].bounds.size.width - 2 * textX; 21 CGSize textMaxSize = CGSizeMake(textW, MAXFLOAT); 22 NSDictionary *textAttrs = @{NSFontAttributeName : [UIFont systemFontOfSize:14]}; 23 CGFloat textH = [status.text boundingRectWithSize:textMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:textAttrs context:nil].size.height; 24 CGRect text_labelFrame = CGRectMake(textX, textY, textW, textH); 25 26 // 配图 27 if (status.picture) { 28 CGFloat pictureWH = 100; 29 CGFloat pictureX = textX; 30 CGFloat pictureY = CGRectGetMaxY(text_labelFrame) + margin; 31 CGRect pictureImageViewFrame = CGRectMake(pictureX, pictureY, pictureWH, pictureWH); 32 33 cellHeight = CGRectGetMaxY(pictureImageViewFrame); 34 } else { 35 cellHeight = CGRectGetMaxY(text_labelFrame); 36 } 37 38 cellHeight += margin; 39 40 return cellHeight; 41 }
这样就能达到案例的效果了
虽然能解决上面的问题,但这样的代码看起来很垃圾,因为控制器知道的太多了,计算高度最好在你拿到数据的时候就已经计算好了,只要拿着用就行了
我们可以把计算高度封装到数据模型XMGStatus里
1 /** 2 * 返回每一行cell的具体高度 3 */ 4 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 5 { 6 XMGStatus *status = self.statuses[indexPath.row]; 7 return status.cellHeight; 8 } 9 10 /*************XMGStatus*****************/ 11 #import <UIKit/UIKit.h> 12 13 @interface XMGStatus : NSObject 14 /**** 文字\图片数据 ****/ 15 /** 姓名 */ 16 @property (nonatomic, copy) NSString *name; 17 /** 文本 */ 18 @property (nonatomic, copy) NSString *text; 19 /** 头像 */ 20 @property (nonatomic, copy) NSString *icon; 21 /** 配图 */ 22 @property (nonatomic, copy) NSString *picture; 23 /** 是否为会员 */ 24 @property (nonatomic, assign) BOOL vip; 25 26 /**** frame数据 ****/ 27 /** 头像的frame */ 28 @property (nonatomic, assign) CGRect iconFrame; 29 /** 昵称的frame */ 30 @property (nonatomic, assign) CGRect nameFrame; 31 /** 会员的frame */ 32 @property (nonatomic, assign) CGRect vipFrame; 33 /** 文字的frame */ 34 @property (nonatomic, assign) CGRect textFrame; 35 /** 配图的frame */ 36 @property (nonatomic, assign) CGRect pictureFrame; 37 /** cell的高度 */ 38 @property (nonatomic, assign) CGFloat cellHeight; 39 40 @end 41 42 #import "XMGStatus.h" 43 44 @implementation XMGStatus 45 - (CGFloat)cellHeight 46 { 47 if (_cellHeight == 0) { 48 CGFloat margin = 10; 49 50 // 头像 51 CGFloat iconX = margin; 52 CGFloat iconY = margin; 53 CGFloat iconWH = 30; 54 self.iconFrame = CGRectMake(iconX, iconY, iconWH, iconWH); 55 56 // 昵称(姓名) 57 CGFloat nameY = iconY; 58 CGFloat nameX = CGRectGetMaxX(self.iconFrame) + margin; 59 // 计算文字所占据的尺寸 60 NSDictionary *nameAttrs = @{NSFontAttributeName : [UIFont systemFontOfSize:17]}; 61 CGSize nameSize = [self.name sizeWithAttributes:nameAttrs]; 62 self.nameFrame = (CGRect){{nameX, nameY}, nameSize}; 63 64 // 会员图标 65 if (self.vip) { 66 CGFloat vipW = 14; 67 CGFloat vipH = nameSize.height; 68 CGFloat vipY = nameY; 69 CGFloat vipX = CGRectGetMaxX(self.nameFrame) + margin; 70 self.vipFrame = CGRectMake(vipX, vipY, vipW, vipH); 71 } 72 73 // 文字 74 CGFloat textX = iconX; 75 CGFloat textY = CGRectGetMaxY(self.iconFrame) + margin; 76 CGFloat textW = [UIScreen mainScreen].bounds.size.width - 2 * textX; 77 CGSize textMaxSize = CGSizeMake(textW, MAXFLOAT); 78 NSDictionary *textAttrs = @{NSFontAttributeName : [UIFont systemFontOfSize:14]}; 79 CGFloat textH = [self.text boundingRectWithSize:textMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:textAttrs context:nil].size.height; 80 self.textFrame = CGRectMake(textX, textY, textW, textH); 81 82 // 配图 83 if (self.picture) { 84 CGFloat pictureWH = 100; 85 CGFloat pictureX = textX; 86 CGFloat pictureY = CGRectGetMaxY(self.textFrame) + margin; 87 self.pictureFrame = CGRectMake(pictureX, pictureY, pictureWH, pictureWH); 88 89 _cellHeight = CGRectGetMaxY(self.pictureFrame); 90 } else { 91 _cellHeight = CGRectGetMaxY(self.textFrame); 92 } 93 _cellHeight += margin; 94 } 95 return _cellHeight; 96 } 97 @end 98 99 100 那么我们在XMGStatusCell.m布局子控件就可以这样写 101 /** 102 * 布局子控件 103 */ 104 - (void)layoutSubviews 105 { 106 [super layoutSubviews]; 107 108 self.iconImageView.frame = self.status.iconFrame; 109 self.nameLabel.frame = self.status.nameFrame; 110 self.vipImageView.frame = self.status.vipFrame; 111 self.text_label.frame = self.status.textFrame; 112 self.pictureImageView.frame = self.status.pictureFrame; 113 } 114 当然也可以直接在设置控件数据时布局(因为在给cell赋值时使用了setStatus:(XMGStatus *)status方法) 115 /** 116 * 设置子控件显示的数据 117 */ 118 - (void)setStatus:(XMGStatus *)status 119 { 120 _status = status; 121 122 self.iconImageView.image = [UIImage imageNamed:status.icon]; 123 self.nameLabel.text = status.name; 124 self.text_label.text = status.text; 125 126 if (status.isVip) { 127 self.vipImageView.hidden = NO; 128 self.nameLabel.textColor = [UIColor orangeColor]; 129 } else { 130 self.vipImageView.hidden = YES; 131 self.nameLabel.textColor = [UIColor blackColor]; 132 } 133 134 if (status.picture) { 135 self.pictureImageView.hidden = NO; 136 self.pictureImageView.image = [UIImage imageNamed:status.picture]; 137 } else { 138 self.pictureImageView.hidden = YES; 139 } 140 141 self.iconImageView.frame = status.iconFrame; 142 self.nameLabel.frame = status.nameFrame; 143 self.vipImageView.frame = status.vipFrame; 144 self.text_label.frame = status.textFrame; 145 self.pictureImageView.frame = status.pictureFrame; 146 }
运行程序效果就和案例一样
三、自定义不等高cell-storyboard(无配图)
除了代码自定义不等高cell,我们还可以直接用storyboard来自定义cell,相对来说就 简单很多,我们先来看下没有配图的情况
1、首先创建一个cell模型,设置好约束
2、创建一个一个cell模型类,继承UITableViewCell,并且对应着cell模型连线,设置数据
1 /*******************XMGStatusCell.m*********************/ 2 #import "XMGStatusCell.h" 3 #import "XMGStatus.h" 4 5 @interface XMGStatusCell() 6 /** 头像 */ 7 @property (nonatomic, weak) IBOutlet UIImageView *iconImageView; 8 /** 名称 */ 9 @property (nonatomic, weak) IBOutlet UILabel *nameLabel; 10 /** 会员图标 */ 11 @property (nonatomic, weak) IBOutlet UIImageView *vipImageView; 12 /** 文字 */ 13 @property (nonatomic, weak) IBOutlet UILabel *text_label; 14 @end 15 16 @implementation XMGStatusCell 17 18 /** 19 * 设置子控件显示的数据 20 */ 21 - (void)setStatus:(XMGStatus *)status 22 { 23 _status = status; 24 25 self.iconImageView.image = [UIImage imageNamed:status.icon]; 26 self.nameLabel.text = status.name; 27 self.text_label.text = status.text; 28 29 if (status.vip) { 30 self.vipImageView.hidden = NO; 31 self.nameLabel.textColor = [UIColor orangeColor]; 32 } else { 33 self.vipImageView.hidden = YES; 34 self.nameLabel.textColor = [UIColor blackColor]; 35 } 36 } 37 @end
3、创建数据模型类XMGStatus,在控制器实现数据源方法;
值得一提的是在返回cell之前必须先告诉tableView所有cell的估算高度,那么可以在viewDidLoad中写上下面这句:
self.tableView.estimatedRowHeight = 44; // 估算每一行的高度
而且:必须告诉tableView所有cell的真实高度是自动计算(根据设置的约束来计算)
self.tableView.rowHeight = UITableViewAutomaticDimension;
iOS8开始:self-sizing
如果没写这两句,运行出来的高度都是不对的
1 #import "ViewController.h" 2 #import "XMGStatus.h" 3 #import "MJExtension.h" 4 #import "XMGStatusCell.h" 5 6 @interface ViewController () 7 /** 微博数据 */ 8 @property (nonatomic, strong) NSArray *statuses; 9 @end 10 11 @implementation ViewController 12 13 NSString *ID = @"status"; 14 15 - (NSArray *)statuses 16 { 17 if (!_statuses) { 18 _statuses = [XMGStatus objectArrayWithFilename:@"statuses.plist"]; 19 } 20 return _statuses; 21 } 22 23 - (void)viewDidLoad { 24 [super viewDidLoad]; 25 26 // 告诉tableView所有cell的真实高度是自动计算(根据设置的约束来计算) 27 self.tableView.rowHeight = UITableViewAutomaticDimension; 28 // 告诉tableView所有cell的估算高度 29 self.tableView.estimatedRowHeight = 44; 30 } 31 32 #pragma mark - <数据源> 33 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 34 { 35 return self.statuses.count; 36 } 37 38 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 39 { 40 XMGStatusCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; 41 42 cell.status = self.statuses[indexPath.row]; 43 44 return cell; 45 } 46 @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
1 #import <UIKit/UIKit.h> 2 @class XMGStatus; 3 4 @interface XMGStatusCell : UITableViewCell 5 /** 模型数据 */ 6 @property (nonatomic, strong) XMGStatus *status; 7 @end
XMGStatusCell.m
1 /** 2 * 设置子控件显示的数据 3 */ 4 - (void)setStatus:(XMGStatus *)status 5 { 6 _status = status; 7 8 self.iconImageView.image = [UIImage imageNamed:status.icon]; 9 self.nameLabel.text = status.name; 10 self.text_label.text = status.text; 11 12 if (status.vip) { 13 self.vipImageView.hidden = NO; 14 self.nameLabel.textColor = [UIColor orangeColor]; 15 } else { 16 self.vipImageView.hidden = YES; 17 self.nameLabel.textColor = [UIColor blackColor]; 18 } 19 20 // 设置配图数据 21 if (status.picture) { // 有配图 22 self.pictureHeight.constant = 100; 23 self.pictureBottom.constant = 10; 24 self.pictureImageView.image = [UIImage imageNamed:status.picture]; 25 } else { // 没有配图 26 // 设置图片高度为0 27 self.pictureHeight.constant = 0; 28 // 设置图片底部间距为0 29 self.pictureBottom.constant = 0; 30 } 31 }
XMGStatus.h
1 #import <UIKit/UIKit.h> 2 3 @interface XMGStatus : NSObject 4 /**** 文字\图片数据 ****/ 5 /** 姓名 */ 6 @property (nonatomic, copy) NSString *name; 7 /** 文本 */ 8 @property (nonatomic, copy) NSString *text; 9 /** 头像 */ 10 @property (nonatomic, copy) NSString *icon; 11 /** 配图 */ 12 @property (nonatomic, copy) NSString *picture; 13 /** 是否为会员 */ 14 @property (nonatomic, assign) BOOL vip; 15 @end
XMGStatus.m
1 #import "XMGStatus.h" 2 3 @implementation XMGStatus 4 5 @end
1 #import "ViewController.h" 2 #import "XMGStatus.h" 3 #import "MJExtension.h" 4 #import "XMGStatusCell.h" 5 6 @interface ViewController () 7 /** 微博数据 */ 8 @property (nonatomic, strong) NSArray *statuses; 9 @end 10 11 @implementation ViewController 12 13 NSString *ID = @"status"; 14 15 - (NSArray *)statuses 16 { 17 if (!_statuses) { 18 _statuses = [XMGStatus objectArrayWithFilename:@"statuses.plist"]; 19 } 20 return _statuses; 21 } 22 23 - (void)viewDidLoad { 24 [super viewDidLoad]; 25 26 // iOS8开始:self-sizing 27 28 // 告诉tableView所有cell的真实高度是自动计算(根据设置的约束来计算) 29 self.tableView.rowHeight = UITableViewAutomaticDimension; 30 // 告诉tableView所有cell的估算高度 31 self.tableView.estimatedRowHeight = 44; 32 } 33 34 #pragma mark - <数据源> 35 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 36 { 37 return self.statuses.count; 38 } 39 40 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 41 { 42 XMGStatusCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; 43 44 cell.status = self.statuses[indexPath.row]; 45 46 return cell; 47 } 48 49 50 @end
运行结果
五、最终代码
在以前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前将高度算好
1 #import "ViewController.h" 2 #import "XMGStatus.h" 3 #import "MJExtension.h" 4 #import "XMGStatusCell.h" 5 6 @interface ViewController () 7 /** 微博数据 */ 8 @property (nonatomic, strong) NSArray *statuses; 9 @end 10 11 @implementation ViewController 12 13 NSString *ID = @"status"; 14 15 - (NSArray *)statuses 16 { 17 if (!_statuses) { 18 _statuses = [XMGStatus objectArrayWithFilename:@"statuses.plist"]; 19 } 20 return _statuses; 21 } 22 23 - (void)viewDidLoad { 24 [super viewDidLoad]; 25 // 告诉tableView所有cell的估算高度(设置了估算高度,就可以减少tableView:heightForRowAtIndexPath:方法的调用次数) 26 self.tableView.estimatedRowHeight = 200; 27 } 28 29 #pragma mark - <数据源> 30 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 31 { 32 return self.statuses.count; 33 } 34 35 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 36 { 37 XMGStatusCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; 38 39 cell.status = self.statuses[indexPath.row]; 40 41 return cell; 42 } 43 44 #pragma mark - <代理方法> 45 XMGStatusCell *cell; 46 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 47 { 48 49 // 创建一个cell(cell的作用:根据模型数据布局所有的子控件,进而计算出cell的高度) 50 if (!cell) { 51 cell = [tableView dequeueReusableCellWithIdentifier:ID]; 52 } 53 // 设置模型数据 54 cell.status = self.statuses[indexPath.row]; 55 return cell.height; 56 } 57 @end 58 /****************** XMGStatusCell.m **********************/ 59 #import "XMGStatusCell.h" 60 #import "XMGStatus.h" 61 62 @interface XMGStatusCell() 63 /** 头像 */ 64 @property (nonatomic, weak) IBOutlet UIImageView *iconImageView; 65 /** 名称 */ 66 @property (nonatomic, weak) IBOutlet UILabel *nameLabel; 67 /** 会员图标 */ 68 @property (nonatomic, weak) IBOutlet UIImageView *vipImageView; 69 /** 文字 */ 70 @property (nonatomic, weak) IBOutlet UILabel *text_label; 71 /** 配图 */ 72 @property (nonatomic, weak) IBOutlet UIImageView *pictureImageView; 73 @end 74 75 @implementation XMGStatusCell 76 77 - (void)awakeFromNib 78 { 79 // 如果lable有自动换行的情况时 80 // 手动设置文字的最大宽度(目的是:让label知道自己文字的最大宽度,进而能够计算出自己的frame) 81 self.text_label.preferredMaxLayoutWidth = [UIScreen mainScreen].bounds.size.width - 20; 82 } 83 84 /** 85 * 设置子控件显示的数据 86 */ 87 - (void)setStatus:(XMGStatus *)status 88 { 89 _status = status; 90 91 self.iconImageView.image = [UIImage imageNamed:status.icon]; 92 self.nameLabel.text = status.name; 93 self.text_label.text = status.text; 94 95 if (status.vip) { 96 self.vipImageView.hidden = NO; 97 self.nameLabel.textColor = [UIColor orangeColor]; 98 } else { 99 self.vipImageView.hidden = YES; 100 self.nameLabel.textColor = [UIColor blackColor]; 101 } 102 103 // 设置配图数据 104 if (status.picture) { // 有配图 105 self.pictureImageView.hidden = NO; 106 self.pictureImageView.image = [UIImage imageNamed:status.picture]; 107 } else { // 没有配图 108 self.pictureImageView.hidden = YES; 109 } 110 } 111 112 - (CGFloat)height 113 { 114 // 强制布局cell内部的所有子控件(label根据文字多少计算出自己最真实的尺寸) 115 [self layoutIfNeeded]; 116 117 // 计算cell的高度 118 if (self.status.picture) { 119 return CGRectGetMaxY(self.pictureImageView.frame) + 10; 120 } else { 121 return CGRectGetMaxY(self.text_label.frame) + 10; 122 } 123 } 124 @end