iOS开发——UI进阶篇(三)自定义不等高cell,如何拿到cell的行高,自动计算cell高度,(有配图,无配图)微博案例

一、纯代码自定义不等高cell


废话不多说,直接来看下面这个例子
先来看下微博的最终效果

首先创建一个继承UITableViewController的控制器
@interface ViewController : UITableViewController
创建一个cell模型
@interface XMGStatusCell : UITableViewCell
再创建微博的数据模型
@interface XMGStatus : NSObject

纯代码自定义不等高cell

和前面等高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

 

 

posted @ 2015-07-23 23:21  Mr.陳  阅读(4129)  评论(1编辑  收藏  举报