美文网首页iOS 一般知识点iOS开发iOS在路上
iOS UITableView5种行高自适应方案的选择

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

作者: yyggzc521 | 来源:发表于2018-11-22 14:25 被阅读338次

    下面将给出了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/

    相关文章

      网友评论

        本文标题:iOS UITableView5种行高自适应方案的选择

        本文链接:https://www.haomeiwen.com/subject/cazkqqtx.html