一:iOS項目開發中,需要動態返回行高自定義cell的場景可以說是數不過來,可以不誇張的說,只要服務器返回的同一個字段的文字字數無限制,那麼我們客戶端在設置的時候就要動態返回行高。
場景:1.當需要tableview展示數據時,一般頭像,昵稱,等信息都是有限制的,但對於狀態(說說,心情)等都是不固定的。
demo介紹:本demo在實現這個功能的時候,考慮到了性能方面,由於行高方法會頻繁調用。。。。。。。。代碼中會有注釋一一說明,請認真讀完
二:加*****的地方要仔細看哦。先直接上代碼給你看viewcontroller中動態返回行高的核心代碼,後介紹具體的所有代碼
2.1#pragma mark - UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *status =self.arrDataSource[indexPath.row];
// 當展示到這一行時,直接調用HUserStatusCell的接口方法,傳入這一行的內容,有多高cell自己算好,返回回來就行。
// 遵循MVC設計模式,你cell多高,控制器不需要知道,自己內部算好返回,控制器只知道結果就行
CGFloat statusHeight = [HUserStatusCelltableView:tableViewrowHeightForObject:status];
CGFloat iconHeight =70;
return statusHeight + iconHeight;
}
2.2HUserStatusCell中改方法的實現
+ (CGFloat)tableView:(UITableView *)tableView rowHeightForObject:(id)object
{
CGFloat statusLabelWidth =150;
//字符串分類提供方法,計算字符串的高度,還是同樣道理,字符串有多高,cell也不需要知道,參數傳給你,具體怎麼算不管,字符串NSString自己算好返回來就行
CGSize statusLabelSize =[objectsizeWithLabelWidth:statusLabelWidthfont:[UIFontsystemFontOfSize:17]];
return statusLabelSize.height;
}
#import "NSString+StringSize.h"
2.3字符串的分類,計算高度
@implementation NSString (StringSize)
- (CGSize)sizeWithLabelWidth:(CGFloat)width font:(UIFont *)font{
NSDictionary *dict=@{NSFontAttributeName : font};
CGRect rect=[selfboundingRectWithSize:CGSizeMake(width,MAXFLOAT)options:(NSStringDrawingUsesLineFragmentOrigin)attributes:dictcontext:nil];
CGFloat sizeWidth=ceilf(CGRectGetWidth(rect));
CGFloat sizeHieght=ceilf(CGRectGetHeight(rect));
return CGSizeMake(sizeWidth, sizeHieght);
}
@end
三:現在說說具體的所有代碼
3.1:控制器中的所有代碼,
#import "ViewController.h"
#import "HUserStatusCell.h"
@interface ViewController ()
@property (nonatomic,strong)UITableView *tableView;
@property (nonatomic,copy )NSArray *arrDataSource;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.viewaddSubview:self.tableView];
// self.tableView.tableFooterView = [[UIView alloc] init];
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.arrDataSource.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
HUserStatusCell *cell = [HUserStatusCelluserStatusCellWithTableView:tableView];
[cell setCellDataWithStatusName:self.arrDataSource[indexPath.row]];
return cell;
}
#pragma mark - UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *status =self.arrDataSource[indexPath.row];
// *****************************************
// 這裡忍不住多說兩句,之前在寫動態返回行高的時候,我把服務器返回的數據放在數據源中(一開始10條數據),依次計算好數據源模型中的每一個屬性值,先是預留好頭像的高度,然後再計算文字(狀態,說說之類的數據)的高度,總的高度算好,放入行高數組裡,然後將數據全部計算好都放入行高數組,所有的邏輯是在控制器中,耦合行太強,不遵循MVC的設計思想,關鍵性能方面也不好,比如我們當前的屏幕只能展示兩條數據,照這種做法我計算了10個行高,而且方法都掉了,很明顯浪費內存啊。
// 通過這種方式,你要展示了,我就計算好行高展示,需要展示時才計算行高,性能方面也提高了。 而且控制器的可讀性也大大增強了,因為處理的邏輯少了。
// 另外:如果你覺得性能不高,還可以優化,創建空的行高數組,剛開始返回行高數組數據,數據為空,計算高度,在加到行高數組中,那麼當滑倒最底部的時候,行高數組也緩存了所有數據,此時在來回滑動直接從行高數組取了(注意:行高數組一開始為空,會崩潰,要做判空處理,可以問我要demo,裡面我都封好了額);
CGFloat statusHeight = [HUserStatusCelltableView:tableViewrowHeightForObject:status];
CGFloat iconHeight =70;
return statusHeight + iconHeight;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[tableView deselectRowAtIndexPath:indexPathanimated:YES];
}
#pragma mark - lazy
- (UITableView *)tableView{
if (!_tableView) {
_tableView = [[UITableViewalloc]initWithFrame:self.view.frame];
_tableView.delegate =self;
_tableView.dataSource =self;
}
return _tableView;
}
- (NSArray *)arrDataSource{
if (!_arrDataSource) {
NSString *string1 =@"這是只是一個菜鳥沒事寫的小程序,希望能夠幫助到您";
NSString *string2 =@"如很多APP一樣,這裡的自定義的cell需要5花八門,比如頭像昵稱是不變的";
NSString *string3 =@"但是總有變的,比如涉及到社區時,用戶的狀態,簽名等";
NSString *string4 =@"舅舅來我家,給我們幾個小孩發紅包,叫我們打開微信,他發紅包,他們手機都戳破了最高也就十元。我說我沒微信,他在父母的注視下給了我一百,呵呵";
NSString *string5 =@"圖書館發生的真實一幕:一男一女,應該是情侶,在圖書館開門時往裡沖的過程中女生不幸摔倒,男生剛要回去扶起她來,只聽女生大喊:“不要管我,去占座!";
NSString *string6 =@"一個意大利帥比接受采訪,說道:“東方女孩子化妝太厲害了!卸完妝完全是另一個人!本以為他要開始吐槽,結果帥比繼續興奮的說:“有一種一次交往兩個人的感覺!真的很賺";
NSString *string7 =@"小明因為自己是個快槍手十分苦惱,在網上看別人說快要射的時候換個姿勢就可以延長時間,於是決定約個妹子試試看。當天晚上,“啪”的一聲之後,妹子怒道:“你TM的練武術呢?一分鐘換二十多個姿勢!";
NSString *string8 =@"小明因為自己是個快槍手十分苦惱,在網上看別人說快要射的時候換個姿勢就可以延長時間,於是決定約個妹子試試看。當天晚上,“啪”的一聲之後,妹子怒道:“你TM的練武術呢?一分鐘換二十多個姿勢!";
NSString *string9 =@"小明因為自己是個快槍手十分苦惱,在網上看別人說快要射的時候換個姿勢就可以延長時間,於是決定約個妹子試試看。當天晚上,“啪”的一聲之後,妹子怒道:“你TM的練武術呢?一分鐘換二十多個姿勢!";
NSString *string10 =@"小明因為自己是個快槍手十分苦惱,在網上看別人說快要射的時候換個姿勢就可以延長時間,於是決定約個妹子試試看。當天晚上,“啪”的一聲之後,妹子怒道:“你TM的練武術呢?一分鐘換二十多個姿勢!";
_arrDataSource =@[string1, string2, string3, string4, string5, string6, string7, string8, string9, string10];
}
return _arrDataSource;
}
@end
3.2:自定義HUserStatusCell 的.h文件,提供三個接口方法,(還是mvc的思想,設置數據,提供接口,.h中盡量不暴露屬性)
#import
@interface HUserStatusCell : UITableViewCell
/**
* 返回復用的HUserStatusCell
*
* @param tableView 當前展示的tableView
*/
+ (instancetype)userStatusCellWithTableView:(UITableView *)tableView;
/**
* 設置cell的數據,提供接口
*
* @param status 狀態字符串
*/
- (void)setCellDataWithStatusName:(NSString *)status;
/**
* 傳入每一行cell數據,返回行高,提供接口
*
* @param tableView 當前展示的tableView
* @param object cell的展示數據內容
*/
+ (CGFloat)tableView:(UITableView *)tableView rowHeightForObject:(id)object;
@end
自定義HUserStatusCell 的.m文件,具體實現了復用的代碼,接口的實現
#import "HUserStatusCell.h"
#import "NSString+StringSize.h"
@interface HUserStatusCell ()
@property (nonatomic,strong)UIImageView *iconImageView;
@property (nonatomic,strong)UILabel *nickNameLabel;
@property (nonatomic,strong)UILabel *statusLabel;
@end
@implementation HUserStatusCell
#pragma mark - init
+ (instancetype)userStatusCellWithTableView:(UITableView *)tableView{
static NSString *cellidentifier =@"SCYImageViewCell";
HUserStatusCell *cell = [tableViewdequeueReusableCellWithIdentifier:cellidentifier];
if (!cell) {
cell = [[HUserStatusCellalloc]initWithStyle:UITableViewCellStyleDefaultreuseIdentifier:cellidentifier];
}
return cell;
}
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
self = [superinitWithStyle:stylereuseIdentifier:reuseIdentifier];
if (self) {
[self.contentViewaddSubview:self.iconImageView];
[self.contentViewaddSubview:self.nickNameLabel];
[self.contentViewaddSubview:self.statusLabel];
}
return self;
}
#pragma mark - public
- (void)setCellDataWithStatusName:(NSString *)status{
self.statusLabel.text = status;
self.nickNameLabel.text =@"小海原創";
self.iconImageView.image = [UIImageimageNamed:@"zth"];
}
+ (CGFloat)tableView:(UITableView *)tableView rowHeightForObject:(id)object
{
CGFloat statusLabelWidth =150;
//字符串分類提供方法,計算字符串的高度
// *****************************************
CGSize statusLabelSize =[objectsizeWithLabelWidth:statusLabelWidthfont:[UIFontsystemFontOfSize:17]];
return statusLabelSize.height;
}
#pragma mark - private
- (void)layoutSubviews{
[super layoutSubviews];
self.iconImageView.frame =CGRectMake(10,10,40,40);
self.nickNameLabel.frame =CGRectMake(60,20,100,20);
CGFloat statusLabelWidth =150;//限制寬度
// *****************************************
//根據實際內容,返回高度,
CGSize statusLabelSize = [self.statusLabel.textsizeWithLabelWidth:statusLabelWidthfont:[UIFontsystemFontOfSize:15]];
self.statusLabel.frame =CGRectMake(60,60,self.frame.size.width -60, statusLabelSize.height);
}
#pragma mark - lazy
- (UIImageView *)iconImageView{
if (!_iconImageView) {
_iconImageView = [[UIImageViewalloc]init];
_iconImageView.layer.cornerRadius =4;
_iconImageView.layer.masksToBounds =YES;
// 這兩句代碼作用非常代大,開發中調試看各個子控件是否重疊非常有效
// _iconImageView.layer.borderWidth = 0.5;
// _iconImageView.layer.borderColor = [UIColor redColor].CGColor;
}
return _iconImageView;
}
- (UILabel *)nickNameLabel{
if (!_nickNameLabel) {
_nickNameLabel = [[UILabel alloc] init];
_nickNameLabel.textColor = [UIColor cyanColor];
// _nickNameLabel.layer.borderWidth = 0.5;
// _nickNameLabel.layer.borderColor = [UIColor redColor].CGColor;
}
return _nickNameLabel;
}
- (UILabel *)statusLabel{
if (!_statusLabel) {
_statusLabel = [[UILabel alloc] init];
_statusLabel.textAlignment = NSTextAlignmentLeft;
_statusLabel.numberOfLines = 0;
// _statusLabel.textColor = [UIColor cyanColor];
// _statusLabel.layer.borderWidth = 0.5;
// _statusLabel.layer.borderColor = [UIColor redColor].CGColor;
}
return _statusLabel;
}
@end
3.3: 最後NSString的分類中擴充一個計算高度的方法
.h文件中
@interface NSString (StringSize)
/**
* 簡單計算textsize
*
* @param width 傳入特定的寬度
* @param font 字體
*/
- (CGSize)sizeWithLabelWidth:(CGFloat)width font:(UIFont *)font;
@end
.m文件中#import "NSString+StringSize.h"
@implementation NSString (StringSize)
// *****************************************
- (CGSize)sizeWithLabelWidth:(CGFloat)width font:(UIFont *)font{
NSDictionary *dict=@{NSFontAttributeName : font};
CGRect rect=[selfboundingRectWithSize:CGSizeMake(width,MAXFLOAT)options:(NSStringDrawingUsesLineFragmentOrigin)attributes:dictcontext:nil];
CGFloat sizeWidth=ceilf(CGRectGetWidth(rect));
CGFloat sizeHieght=ceilf(CGRectGetHeight(rect));
return CGSizeMake(sizeWidth, sizeHieght);
}
@end
四,還是上面說的,設置行高數組再增加性能.(重要的地方我都打了注釋)計算cell的方法在將要展示的時候,都會計算下行高,如果已經計算過,就沒必要再計算,這時候,我們只要將計算下來的行高數據緩存下就行。
4.1在heightForRowAtIndexPath設置行高數組
#pragma mark - UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
// 如果數組中沒有這條數據,那麼取數據的時候就直接crash,相信開發中這種數組越界的情況太常見了。
// 這裡給數組設置分類,提供了方法,返回nil值,就不會奔潰,下面上數組的分類方法
// 通過看打印值就能清楚的看到好處。
NSNumber *cellHeight = [self.heightArrayh_safeObjectAtIndex:indexPath.row];
if (cellHeight) {
NSLog(@"不用計算,直接返回行高了");
return [cellHeightfloatValue];
}else{
NSString *status =self.arrDataSource[indexPath.row];
CGFloat statusHeight = [HUserStatusCelltableView:tableViewrowHeightForObject:status];
CGFloat iconHeight =70;
[self.heightArrayaddObject:@(statusHeight + iconHeight)];
NSLog(@"第一次加載計算一次,每次展示都計算一次");
return statusHeight + iconHeight;
}
}
4.2 數組的分類方法,保證程序不會奔潰,開發中這樣設置,就再也不用擔心數組越界了;
@implementation NSArray (Safe)
- (id)h_safeObjectAtIndex:(NSUInteger)index{
if(self.count ==0) {
NSLog(@"--- mutableArray have no objects ---");
return (nil);
}
if(index >MAX(self.count -1,0)) {
NSLog(@"--- index:%li out of mutableArray range ---", (long)index);
return (nil);
}
return ([selfobjectAtIndex:index]);
}
@end