iOS UITableView5种行高自适应方案的选择

下面将给出了5种Cell自适应高度的方案,并对比每种实现方案的流畅度。从UI最不流畅的一种开始,我们慢慢优化。通过观察屏幕的FPS来判断屏幕在操作时是否卡顿。关于对FPS的实时监测,使用了YYKit-Demo中FPS控件来实现。点击列表中不同Cell都会跳转相同的内容列表页。只不过每个Cell所对应的内容页面的Cell自适应高度的实现方式不同。


5种Cell高度自适应方案

1.Autolayout + AutomaticDimension

点击第一个Cell进入的页面完全由AutoLayout进行布局,Cell自适应的高度也不用我们自己计算,而是使用系统提供的解决方案UITableViewAutomaticDimension来解决。当然,使用UITableViewAutomaticDimension要依赖于你添加的约束,稍后会介绍到。这种实现方案用起来简单,不过UI流畅度方面不太理想。当TableView快速滑动时,就会出现严重的掉帧。亲测FPS最低值38!


545446-20160922151158059-1130164887.gif
#//第1步:设置预估值
self.tableView.estimatedRowHeight = 100.0;  

#//第2步:返回UITableViewAutomaticDimension 自动调整约束,性能非常低
-  (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    return UITableViewAutomaticDimension;      
}
cell所有子控件和底部动态label的约束
动态label关键的约束,撑开cell

2.Autolayout + CountHeight

依然是采用AutoLayout的方式来对Cell的内容进行布局,不过Cell的高度我们是自己计算的,计算的过程是放在子线程中进行的,所以这种实现方式要优于第一种实现方式,亲测FPS最低值36!


手动计算行高,Autolayout布局
- (void)createDataSupport {

    self.dataSupport = [[DataSupport alloc] init];
    __weak typeof (self) weak_self = self;
    [self.dataSupport setUpdataDataSourceBlock:^(NSMutableArray *dataSource) {
        weak_self.dataSource = dataSource;
        [weak_self.tableView reloadData];
    }];

    [self addTestData];
}

- (void)addTestData {

    dispatch_queue_t concurrentQueue = dispatch_queue_create("zeluli.concurrent", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_semaphore_t lock = dispatch_semaphore_create(1);
    
    for (int i = 0; i < 50; i ++) {
        dispatch_group_async(group, concurrentQueue, ^{
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
            [self createTestModel];
            dispatch_semaphore_signal(lock);
        });
    }
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        [self updateDataSource];
    });
}

- (void)createTestModel {
    TestDataModel * model = [[TestDataModel alloc] init];
    model.title = @"行歌";
    
    NSDateFormatter *dataFormatter = [[NSDateFormatter alloc] init];
    [dataFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
    model.time = [dataFormatter stringFromDate:[NSDate date]];
    
    NSString *imageName = [NSString stringWithFormat:@"%d.jpg", arc4random() % 6];
    model.imageName =imageName;
    
    NSInteger endIndex = arc4random() % contentText.length;
    model.content = [contentText substringToIndex:endIndex];
    
    model.textHeight = [self countTextHeight:model.content];
    model.cellHeight = model.textHeight + 60;
    
    NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:model.content];
    text.font = [UIFont systemFontOfSize:14];
    text.lineSpacing = 3;
    model.attributeContent = text;
    
    model.attributeTitle = [[NSAttributedString alloc] initWithString:model.title];
    model.attributeTime = [[NSAttributedString alloc] initWithString:model.time];
    
    [self.dataSource addObject:model];
}

-(CGFloat)countTextHeight:(NSString *) text {

    NSMutableAttributedString *attributeString = [[NSMutableAttributedString alloc] initWithString:text];
    NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
    style.lineSpacing = 0;
    UIFont *font = [UIFont systemFontOfSize:14];
    [attributeString addAttribute:NSParagraphStyleAttributeName value:style range:NSMakeRange(0, text.length)];
    [attributeString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, text.length)];
    NSStringDrawingOptions options = NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading;
    CGRect rect = [attributeString boundingRectWithSize:CGSizeMake(SCREEN_WIDTH - 30, CGFLOAT_MAX) options:options context:nil];

    return rect.size.height + 40;
}

- (void)updateDataSource {

    if (self.updateDataBlock != nil) {
        self.updateDataBlock(self.dataSource);
    }
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    if (indexPath.row < self.dataSource.count) {
        TestDataModel *model = self.dataSource[indexPath.row];
        return model.cellHeight;
    }

    return 100;
}

@implementation AutolayoutTableViewCell

- (void)configCellData:(TestDataModel *)model {//配置cell中的数据
    
    [self.headerImageView setImage:[[ImageCache shareInstance] getCacheImage:model.imageName]];
    [self.titleLable setText:model.title];
    [self.timeLabel setText:model.time];
    [self.contentLabel setText:model.content];
}

3.FrameLayout + CountHeight

为了进一步提高流畅度,我们采用了纯Frame布局,因为Autolayout最终还是会被转换成Frame进行布局的,所以我们就用Frame对整个Cell中的所有子控件进行布局。当然Cell高度及可变内容的高度,跟第2种方法一样都是在子线程中进行计算的,这优化的重要一步。这种实现方式还是比较流畅的,可以作为折中的方案.亲测FPS最低值42!


手动计算行高,frame布局

4.YYKit + CountHeight

545446-20160922151158059-1130164887.gif

接下来我们继续进行优化,引入第三方UI组件YYKit。将Cell上的组件替换成YYKit所提供的组件。然后使用Frame进行布局,当然也是在子线程中对Cell的高度进行了计算。效果还是比较流畅的,但是还未达到完全不掉帧的效果。亲测FPS最低值53!

@property (strong, nonatomic) UIImageView *headerImageView;
@property (strong, nonatomic) YYLabel *titleLable;
@property (strong, nonatomic) YYLabel *timeLabel;
@property (strong, nonatomic) YYTextView *contentTextView;

5.AsyncDisplayKit + CountHeight

我们用Facebook提供的第三方库来进行基础组件的替换,将我们使用到的组件替换成AsyncDisplayKit相应的Note。这些Note是对系统组件的重组,对组件的显示进行了优化,让其渲染更为流畅,亲测FPS最低值59!
如果你对UI流畅度要求比较高的话,那么AsyncDisplayKit是一个比较好的选择。不过会严重依赖AsyncDisplayKit,如果AsyncDisplayKit停止维护了,后期对AsyncDisplayKit进行替换的话,工作量还是比较大的。因为这种布局框架不像网络框架,我们可以对网络框架的调用进行提取,网络层统一对外接口,很方便切换到其他网络请求库。但是像AsyncDisplayKit这种框架会散布于UI层的各个角落,封装提取不易,更不用说轻而易举的替换了。所以像这种页面的实现,个人还是偏向于Framelayout + CountHeight的方式来实现。

@property (strong, nonatomic) ASImageNode *headerImageNode;
@property (strong, nonatomic) ASTextNode *titleTextNode;
@property (strong, nonatomic) ASTextNode *timeTextNode;
@property (strong, nonatomic) ASTextNode *contentTextNode;

总结:

  • 1、2方案对比,手动计算行高优于自适应行高
  • 2、3方案对比,frame优于Autolayout
  • 3、4、5方案, 根据实际情况进行选择,方案5最优,缺点同样明显,侵入性强

参考资料:https://www.cnblogs.com/ludashi/p/5895725.html
参考资料:https://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容