一、純代碼自定義不等高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