【前言】git
在使用华尔街见闻 app 时,看到它的 tableVeiw 上的 cell 具备很好的展开与收缩功能。因而本身想了一下实现,感受应该挺简单的,因而心痒痒写个 demo 实现一波。华尔街见闻 app 上的效果以下:app
【本 demo 实现的效果图】性能
【思路】优化
由它的效果图能够观察出,cell 上默认显示文字多于 4 行时省略,点击时文字所有展示,cell 也同时适应文字的高度。动画
1. label 行数能够用 numberOfLines 属性来控制,改变它就能够改变文字的高度。spa
2. cell 的高度须要变化,可是若是根据文字收缩时算一下高度,展开时又给 cell 一个高度这样会很麻烦,由于这样要去精确计算出文字高度,上下间距。因此这里我想到的是利用 tableView 估算 cell 高度的机制,cell 内的文字用 label 的约束将 cell "撑满"。代理
3. 用一个 model 对应一个 cell 来防止复用数据错乱,而后展开与收缩用 tableView 的刷新动画就行了。code
4. 实际实现过程当中遇到一些小坑,重点在处理动画流畅思路上。blog
【代码实现】get
1. 开启 tableView 估算机制
- (HomeView *)homeView { if (!_homeView) { _homeView = [[HomeView alloc] init]; _homeView.backgroundColor = [UIColor whiteColor]; _homeView.tableView.dataSource = self; _homeView.tableView.delegate = self; _homeView.tableView.estimatedRowHeight = 80; [_homeView.tableView registerClass:[HomeCell class] forCellReuseIdentifier:NSStringFromClass([HomeCell class])]; [self.view addSubview:_homeView]; } return _homeView; }
2. label 默认 numberOfLines 设置
- (UILabel *)contentL { if (!_contentL) { _contentL = [[UILabel alloc] init]; _contentL.frame = CGRectMake(0, 0, self.contentView.bounds.size.width, 0); _contentL.lineBreakMode = NSLineBreakByTruncatingTail; _contentL.numberOfLines = 4; [self.contentView addSubview:_contentL]; } return _contentL; }
3. cell 数据设置 (经过 model 内 isOpen 来防止 cell 复用数据发生错乱)
- (void)setModel:(HomeModel *)model { _model = model; self.contentL.text = model.title; if (model.isOpen) { self.contentL.numberOfLines = NSIntegerMax; } else { self.contentL.numberOfLines = 4; } }
4. 点击时 tableView 动画刷新
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { HomeCell *cell = [tableView cellForRowAtIndexPath:indexPath]; cell.model.isOpen = !cell.model.isOpen; [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; }
【细节优化】
上面的实现看似美好且不费力,但实际多滑两下,多点一些不一样高度的 cell,会发现它的展开动画明显不流畅,会出现一点抖动情况。以下:
看到这里,展开和收缩动画在这个 cell 上简直抽风了,有种忽然截断了的感受。这是确定不能忍的。
优化分析:
tableView 动画不会有问题,是很正常的刷新对应的 cell。惟一可能出问题的就是 cell 的估算机制。由于 cell 咱们默认给的高度是 80,因此当 cell 实际高度与默认高度相差太大时,就会出现这种问题了。
解决方案:
咱们仍是采用 cell 高度的估算机制,但要让系统尽量估计得准确,减小它调整高度带来的动画不流畅。那就是利用 tableView 返回估算高度的代理方法,咱们先给它算好一个高度,而且这个高度确定基本准确。(注意: 提早在拿到 model 数据时就算好 cell 高度存放在 model 中,而不是在 tableView 代理方法中去计算 cell 高度,由于代理方法调用太频繁,那里作一些简单取值操做就好,否则当数据量大时影响 tableView 滑动性能。)
1. 在 model 获取数据时就算好 cell 数据的高度。在 HomeModel 加一个 cellHeight 属性,并重写 setTitle: 方法。以下:
- (void)setTitle:(NSString *)title { _title = title; CGFloat w = [UIScreen mainScreen].bounds.size.width - 24; CGFloat h = MAXFLOAT; NSDictionary *dict = @{NSFontAttributeName: [UIFont systemFontOfSize:16]}; CGFloat height = [title boundingRectWithSize:CGSizeMake(w, h) options:NSStringDrawingUsesLineFragmentOrigin attributes:dict context:nil].size.height; _cellHeight = height + 24; }
2. 在控制器中加上 tableView 返回估算高度的代理方法设置以下:
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { HomeModel *model = self.testDataArray[indexPath.row]; return model.cellHeight; }
这样再运行,不管怎么滑怎么点,都十分完美。😀
注意: tableView 返回估算高度方法里虽然咱们返回了准确的高度,但 cell 最终的高度并非以估算高度为准,而是取决于 cell 内约束,即 cell 是被约束撑满的。
因此 cell 的实际高度取决于设置数据时的里面 label.numberOfLines。这里返回的高度让系统减小估算的调整,因此这样动画就流畅了。
【demo地址】