天天看点

iOS中最简单实用的自定义动态返回行高的cell,动态计算cell的高度

 一:GitHub 地址点击跳转GitHub地址demo

      iOS项目开发中,需要动态返回行高自定义cell的场景可以说是数不过来,可以不夸张的说,只要服务器返回的同一个字段的文字字数无限制,那么我们客户端在设置的时候就要动态返回行高。

     场景:1.当需要tableview展示数据时,一般头像,昵称,等信息都是有限制的,但对于状态(说说,心情)等都是不固定的。

     demo介绍:本demo在实现这个功能的时候,考虑到了性能方面,由于行高方法会频繁调用。。。。。。。。代码中会有注释一一说明,请认真读完

     废话不多说,先上效果图,界面很简单,主要是理解原理,希望您能够看完

iOS中最简单实用的自定义动态返回行高的cell,动态计算cell的高度

二:加*****的地方要仔细看哦。先直接上代码给你看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 ()<UITableViewDelegate,UITableViewDataSource>

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

@interface HUserStatusCell : UITableViewCell

+ (instancetype)userStatusCellWithTableView:(UITableView *)tableView;

- (void)setCellDataWithStatusName:(NSString *)status;

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

    //字符串分类提供方法,计算字符串的高度

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

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

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

五 ,写到最后 github地址:demo传送门 喜欢的话顺手给个星星Star吧 很高兴您能看到这里,(我的原则是,能封装的代码,绝不能都挤在控制器中看着一坨一坨的,非常恶心);代码的核心部分楼主我也一遍遍码完了,核心思想也做了详细的解释,很多童鞋喜欢看源代码,没事➕楼主扣扣804810354直接给您demo参考。,文中有说的不对的地方欢迎指正。。希望能帮助到您???原创,转载说一声吧。

如果你喜欢这篇文章,或者有任何疑问,可以扫描第一个二维码,加楼主好友哦

也可以扫第二个二维码,关注楼主个人微信公众号。这里有很多生活,职业,技术相关的文章哦。欢迎您的到来。

微信号:

iOS中最简单实用的自定义动态返回行高的cell,动态计算cell的高度

                                             公众号

iOS中最简单实用的自定义动态返回行高的cell,动态计算cell的高度