【前言】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地址】