1. 程式人生 > >iOS中最簡單實用的自定義動態返回行高的cell,動態計算cell的高度

iOS中最簡單實用的自定義動態返回行高的cell,動態計算cell的高度

      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));

returnCGSizeMake(sizeWidth, sizeHieght);

}

@end

三:現在說說具體的所有程式碼

3.1:控制器中的所有程式碼,

#import "ViewController.h"

#import "HUserStatusCell.h"

@interfaceViewController ()<UITableViewDelegate,UITableViewDataSource>

@property (nonatomic,strong)UITableView *tableView;

@property (nonatomic,copy  )NSArray *arrDataSource;

@end

@implementation ViewController

- (void)viewDidLoad {

    [superviewDidLoad];

    [self.viewaddSubview:self.tableView];

//    self.tableView.tableFooterView = [[UIView alloc] init];

}

#pragma mark - UITableViewDataSource

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{

returnself.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 <UIKit/UIKit.h>

@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"

@interfaceHUserStatusCell ()

@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{

staticNSString *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];

    }

returnself;

}

#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 = [UIScreenmainScreen].bounds.size.width - 60;

//字串分類提供方法,計算字串的高度

       //  *****************************************

CGSize statusLabelSize =[objectsizeWithLabelWidth:statusLabelWidthfont:[UIFontsystemFontOfSize:17]];

return statusLabelSize.height;

}

#pragma mark - private

- (void)layoutSubviews{

    [superlayoutSubviews];

self.iconImageView.frame =CGRectMake(10,10,40,40);

self.nickNameLabel.frame =CGRectMake(60,20,100,20);

CGFloat statusLabelWidth =self.frame.size.width -60;//限制寬度

       //  *****************************************

//根據實際內容,返回高度,

CGSize statusLabelSize = [self.statusLabel.textsizeWithLabelWidth:statusLabelWidthfont:[UIFontsystemFontOfSize:15]];

self.statusLabel.frame =CGRectMake(60,60,statusLabelWidth, 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.font = [UIFontsystemFontOfSize:15];

        _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));

returnCGSizeMake(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



,寫到最後 github地址:demo傳送門 喜歡的話順手給個星星Star吧 很高興您能看到這裡,(我的原則是,能封裝的程式碼,絕不能都擠在控制器中看著一坨一坨的,非常噁心);程式碼的核心部分樓主我也一遍遍碼完了,核心思想也做了詳細的解釋,很多童鞋喜歡看原始碼,沒事➕樓主扣扣804810354直接給您demo參考。,文中有說的不對的地方歡迎指正。。希望能幫助到您???原創,轉載說一聲吧。

如果你喜歡這篇文章,或者有任何疑問,可以掃描第一個二維碼,加樓主好友哦

也可以掃第二個二維碼,關注樓主個人微信公眾號。這裡有很多生活,職業,技術相關的文章哦。歡迎您的到來。

微訊號:                                             公眾號