上圖直接給大家看看我們公司業(yè)務(wù)需求吧 以及做出來(lái)的效果圖吗讶。
需求就是這樣 自動(dòng)去適應(yīng)tableview的cell高度 并實(shí)時(shí)計(jì)算高度。
這里我給大家推薦兩種方法去做自適應(yīng)
A: 使用第三方 FDTemplateLayoutCell + Masonry
框架地址:FDTemplateLayoutCell
UITableView+FDTemplateLayoutCell 是一個(gè)由國(guó)人團(tuán)隊(duì)開(kāi)發(fā)的優(yōu)化計(jì)算 UITableViewCell 高度的輕量級(jí)框架恋捆,由于實(shí)現(xiàn)邏輯簡(jiǎn)明清晰照皆,代碼也不復(fù)雜,非常適合作為新手學(xué)習(xí)鸠信。
總結(jié)一下:
1纵寝、iOS8 之前雖然采用 autoLayout 相比 frame layout 得手動(dòng)計(jì)算已經(jīng)簡(jiǎn)化了不少(設(shè)置 estimatedRowHeight 屬性并對(duì)約束設(shè)置正確的 cell 的 contentView 執(zhí)行 systemLayoutSizeFittingSize: 方法)论寨,但還是需要一些模式化步驟星立,同時(shí)還可能遇到一些蛋疼的問(wèn)題比如 UILabel 折行時(shí)的高度計(jì)算爽茴;
2、iOS8 推出 self-sizing cell 后绰垂,一切都變得輕松無(wú)比——做好約束后室奏,直接設(shè)置 estimatedRowHeight 就好了。然而事情并不簡(jiǎn)單劲装,一來(lái)我們依然需要做 iOS7 的適配胧沫,二來(lái) self-sizing 并不存在緩存機(jī)制,不論何時(shí)都會(huì)重新計(jì)算 cell 高度占业,導(dǎo)致 iOS8 下頁(yè)面滑動(dòng)時(shí)會(huì)有明顯的卡頓绒怨。
因此,這個(gè)框架的目的谦疾,引用陽(yáng)神的原話南蹂,就是“既有 iOS8 self-sizing 功能簡(jiǎn)單的 API,又可以達(dá)到 iOS7 流暢的滑動(dòng)效果念恍,還保持了最低支持 iOS6”六剥。
好話不多說(shuō) 開(kāi)始吧~~~~~
1.引用 UITableView+FDTemplateLayoutCell.h 類(lèi);
2.注冊(cè)cell
- (void)registerNib:(nullable UINib *)nib forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(5_0);
- (void)registerClass:(nullable Class)cellClass forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
注意 :此處不管你是用代碼還是 XIB 創(chuàng)建的 cell峰伙,必須先進(jìn)行注冊(cè)(類(lèi)似 UICollectionView):(不然 哈哈 你會(huì)懵逼的)
a. 運(yùn)行崩潰報(bào)錯(cuò) NSAssert(templateCell != nil, @"Cell must be registered to table view for identifier - %@", identifier);
原因:官方是這么說(shuō)明的:
和每個(gè) UITableViewCell ReuseID 一一對(duì)應(yīng)的 template layout cell這個(gè) cell 只為了參加高度計(jì)算疗疟,不會(huì)真的顯示到屏幕上;它通過(guò) UITableView 的 -dequeueCellForReuseIdentifier: 方法 lazy 創(chuàng)建并保存瞳氓,所以要求這個(gè) ReuseID 必須已經(jīng)被注冊(cè)到了 UITableView 中策彤,也就是說(shuō),要么是 Storyboard 中的原型 cell顿膨,要么就是使用了 UITableView 的 -registerClass:forCellReuseIdentifier: 或 -registerNib:forCellReuseIdentifier:其中之一的注冊(cè)方法锅锨。
解決:意思就是說(shuō)你需要注冊(cè)cell對(duì)應(yīng)的identifier。
所以說(shuō)呢注冊(cè)是很有必要的一步A滴帧1馗恪!
3.在 tableView: heightForRowAtIndexPath: 代理方法中調(diào)用以下三個(gè)方法之一完成高度獲饶矣健:
/identifier 即 cell 的 identifier恕洲;configuration block 中的代碼應(yīng)與數(shù)據(jù)源方法 tableView: cellForRowAtIndexPath: 中對(duì) cell 的設(shè)置代碼相同方法內(nèi)部將根據(jù)以上兩個(gè)參數(shù)創(chuàng)建與 cell 對(duì)應(yīng)的 template layout cell,這個(gè) cell 只進(jìn)行高度計(jì)算梅割,不會(huì)顯示到屏幕上/
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifierconfiguration:(void (^)(idcell))configuration;// 返回計(jì)算好的高度(無(wú)緩存)
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifiercacheByIndexPath:(NSIndexPath *)indexPathconfiguration:(void (^)(idcell))configuration;// 返回計(jì)算好的高度霜第,并根據(jù) indexPath 內(nèi)部創(chuàng)建與之相應(yīng)的二維數(shù)組緩存高度
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifiercacheByKey:(id<NSCopying>)keyconfiguration:(void (^)(idcell))configuration;// 返回計(jì)算好的高度,內(nèi)部創(chuàng)建一個(gè)字典緩存高度并由使用者指定 key
像醬紫寫(xiě)??
↓
一般來(lái)說(shuō) cacheByIndexPath: 方法最為“傻瓜”户辞,可以直接搞定所用問(wèn)題泌类。cacheByKey: 方法稍顯復(fù)雜(需要關(guān)注數(shù)據(jù)刷新),但在緩存機(jī)制上相比 cacheByIndexPath: 方法更為高效底燎。因此刃榨,像類(lèi)似微博弹砚、新聞這種會(huì)擁有唯一標(biāo)識(shí)的 cell 數(shù)據(jù)模型,更建議使用cacheByKey: 方法枢希。
4.數(shù)據(jù)源變動(dòng)時(shí)的緩存處理是個(gè)值得關(guān)注的問(wèn)題桌吃。
對(duì)于 cacheByIndexPath: 方法,框架內(nèi)對(duì) 9 個(gè)觸發(fā) UITableView 刷新機(jī)制的公有方法分別進(jìn)行了處理苞轿,保證緩存數(shù)組的正確茅诱;同時(shí),還提供了一個(gè) UITableView 分類(lèi)方法:
- (void)fd_reloadDataWithoutInvalidateIndexPathHeightCache;
用于需要刷新數(shù)據(jù)但不想移除原有緩存數(shù)據(jù)(框架內(nèi)對(duì) reloadData 方法的處理是清空緩存)時(shí)調(diào)用搬卒,比如常見(jiàn)的“下拉加載更多數(shù)據(jù)”操作瑟俭。
對(duì)于 cacheByKey: 方法,當(dāng) cell 高度發(fā)生改變時(shí)契邀,必須手動(dòng)處理:
[tableView.fd_keyedHeightCacheinvalidateHeightForKey:key]; // 移除 key 對(duì)應(yīng)的高度緩存
[tableView.fd_keyedHeightCacheinvalidateAllHeightCache]; // 移除所有高度緩存
如果需要查看 debug 打印信息尔当,設(shè)置 fd_debugLogEnabled 屬性:
tableView.fd_debugLogEnabled = YES;
高度獲取
流程
我們直接以 cacheByIndexPath: 方法源碼為例進(jìn)行了解(cacheByKey: 方法的實(shí)現(xiàn)大同小異)
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifiercacheByIndexPath:(NSIndexPath *)indexPathconfiguration:(void (^)(idcell))configuration {
// 1. 如果 identifier 和 indexPath 為空,返回高度為 0
if (!identifier || !indexPath) {
return 0;
}
// 2. 通過(guò) FDIndexPathHeightCache 類(lèi)聲明的方法檢查是否存在相應(yīng)緩存
if ([self.fd_indexPathHeightCacheexistsHeightAtIndexPath:indexPath]) {
// 打印 debug 信息
[self fd_debugLog:[NSStringstringWithFormat:@"hit cache by index path[%@:%@] - %@", @(indexPath.section), @(indexPath.row), @([self.fd_indexPathHeightCacheheightForIndexPath:indexPath])]];
// 提取并返回對(duì)應(yīng)緩存中的額高度
return [self.fd_indexPathHeightCacheheightForIndexPath:indexPath];
}
// 3. 如果沒(méi)有緩存蹂安,通過(guò) fd_heightForCellWithIdentifier: configuration: 方法計(jì)算獲得 cell 高度
CGFloatheight = [self fd_heightForCellWithIdentifier:identifierconfiguration:configuration];
// 4. 通過(guò) FDIndexPathHeightCache 類(lèi)聲明的方法將高度存入緩存
[self.fd_indexPathHeightCachecacheHeight:heightbyIndexPath:indexPath];
// 打印 debug 信息
[self fd_debugLog:[NSStringstringWithFormat: @"cached by index path[%@:%@] - %@", @(indexPath.section), @(indexPath.row), @(height)]];
return height;
}
fd_heightForCellWithIdentifier: configuration: 方法會(huì)根據(jù) identifier 以及 configuration block 提供一個(gè)和 cell 布局相同的 template layout cell椭迎,并將其傳入 fd_systemFittingHeightForConfiguratedCell: 這個(gè)私有方法返回計(jì)算出的高度。
結(jié)合masonry 搭建頁(yè)面時(shí), 設(shè)為固定高度是沒(méi)有問(wèn)題的田盈,但是使用框架緩存cell高度時(shí)畜号,cell重疊所有cell高度都為0。 知道為什么嗎允瞧???
原因:計(jì)算cell高度時(shí)简软,因Y方向上約束不完善,無(wú)法確定cell的高度述暂。
解決:設(shè)置約束時(shí)痹升,由上到下設(shè)置,最后一個(gè)需要設(shè)置距底部邊距畦韭。一定能讓系統(tǒng)確定這個(gè)cell的高度
===============這樣的話就沒(méi)有問(wèn)題啦================
B: Masonry + 手動(dòng)計(jì)算行高
關(guān)于手動(dòng)計(jì)算高度 疼蛾,怎么去做呢∫张洌可以這樣給大家說(shuō) RowHeight = 固定高度 + 非固定高度察郁;
固定:每一個(gè)控件之間的間隙 這個(gè)是固定的 如上圖:用戶(hù)名、收回按鈕同樣也是固定高度转唉。
非固:如上圖中的message信息 (UIlabel)皮钠、和下面的banner 為不固定
1.在tableviewCell里面約束好相關(guān)控件 怎么約束去看看 https://blog.csdn.net/dragongd/article/details/61432393
在使用Masonry約束好相關(guān)控件之后。我們就該去手動(dòng)計(jì)算我們的行高了赠法。
2.自定義一個(gè)Model
.h
#import "BaseModel.h"
@interface GrassdetailModelTwo : BaseModel
@property(nonatomic,assign)CGFloat rowHeight;//行高
-(void)updataModel;
@end
.m
#import "GrassdetailModelTwo.h"
#import "BrandView.h"
@implementation GrassdetailModelTwo
-(void)updataModel{
UILabel * testLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, WIDTH-FitWidth(60), 0)];
testLabel.text = self.describe;
testLabel.font = [UIFont fontWithName:@"PingFangSC-Regular" size:FitValue(14)];
testLabel.numberOfLines = 0;
[testLabel sizeToFit];
CGFloat labelHeight = testLabel.frame.size.height +1;
testLabel = nil;
}
label計(jì)算高度:
(1).boundingRectWithSize:
(2).sizeThatFits
(3).sizeToFit
(4).sizeWithAttributes
計(jì)算行高 這個(gè)其實(shí)有很多種麦轰,個(gè)人推薦用這種 [testLabel sizeToFit]; ?? 其余幾種方法對(duì)于全中文的文本還行,但是對(duì)于英文特別多的時(shí)候,計(jì)算高度可能就會(huì)不準(zhǔn)了款侵。(自己的見(jiàn)解驯嘱,勿噴??)
3.在TableView 代理返回高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSArray * arr = _dataArr[indexPath.section];
GrassdetailModelTwo * model = arr[0];
return model.rowHeight;
}