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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM