下面將給出了5種Cell自適應高度的方案,并對比每種實現(xiàn)方案的流暢度吊档。從UI最不流暢的一種開始判沟,我們慢慢優(yōu)化。通過觀察屏幕的FPS來判斷屏幕在操作時是否卡頓恶导。關于對FPS的實時監(jiān)測浆竭,使用了YYKit-Demo中FPS控件來實現(xiàn)。點擊列表中不同Cell都會跳轉(zhuǎn)相同的內(nèi)容列表頁惨寿。只不過每個Cell所對應的內(nèi)容頁面的Cell自適應高度的實現(xiàn)方式不同邦泄。
1.Autolayout + AutomaticDimension
點擊第一個Cell進入的頁面完全由AutoLayout進行布局,Cell自適應的高度也不用我們自己計算裂垦,而是使用系統(tǒng)提供的解決方案UITableViewAutomaticDimension來解決顺囊。當然,使用UITableViewAutomaticDimension要依賴于你添加的約束蕉拢,稍后會介紹到特碳。這種實現(xiàn)方案用起來簡單,不過UI流暢度方面不太理想晕换。當TableView快速滑動時午乓,就會出現(xiàn)嚴重的掉幀。親測FPS最低值38届巩!
#//第1步:設置預估值
self.tableView.estimatedRowHeight = 100.0;
#//第2步:返回UITableViewAutomaticDimension 自動調(diào)整約束硅瞧,性能非常低
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
2.Autolayout + CountHeight
依然是采用AutoLayout的方式來對Cell的內(nèi)容進行布局,不過Cell的高度我們是自己計算的恕汇,計算的過程是放在子線程中進行的腕唧,所以這種實現(xiàn)方式要優(yōu)于第一種實現(xiàn)方式,親測FPS最低值36瘾英!
- (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中的數(shù)據(jù)
[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最終還是會被轉(zhuǎn)換成Frame進行布局的但惶,所以我們就用Frame對整個Cell中的所有子控件進行布局。當然Cell高度及可變內(nèi)容的高度,跟第2種方法一樣都是在子線程中進行計算的膀曾,這優(yōu)化的重要一步县爬。這種實現(xiàn)方式還是比較流暢的,可以作為折中的方案.親測FPS最低值42添谊!
4.YYKit + CountHeight
接下來我們繼續(xù)進行優(yōu)化,引入第三方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是對系統(tǒng)組件的重組瓣蛀,對組件的顯示進行了優(yōu)化陆蟆,讓其渲染更為流暢,親測FPS最低值59惋增!
如果你對UI流暢度要求比較高的話叠殷,那么AsyncDisplayKit是一個比較好的選擇。不過會嚴重依賴AsyncDisplayKit诈皿,如果AsyncDisplayKit停止維護了林束,后期對AsyncDisplayKit進行替換的話,工作量還是比較大的稽亏。因為這種布局框架不像網(wǎng)絡框架壶冒,我們可以對網(wǎng)絡框架的調(diào)用進行提取,網(wǎng)絡層統(tǒng)一對外接口截歉,很方便切換到其他網(wǎng)絡請求庫胖腾。但是像AsyncDisplayKit這種框架會散布于UI層的各個角落,封裝提取不易瘪松,更不用說輕而易舉的替換了咸作。所以像這種頁面的實現(xiàn),個人還是偏向于Framelayout + CountHeight的方式來實現(xiàn)宵睦。
@property (strong, nonatomic) ASImageNode *headerImageNode;
@property (strong, nonatomic) ASTextNode *titleTextNode;
@property (strong, nonatomic) ASTextNode *timeTextNode;
@property (strong, nonatomic) ASTextNode *contentTextNode;
總結:
- 1记罚、2方案對比,手動計算行高優(yōu)于自適應行高
- 2壳嚎、3方案對比桐智,frame優(yōu)于Autolayout
- 3末早、4、5方案说庭, 根據(jù)實際情況進行選擇然磷,方案5最優(yōu),缺點同樣明顯刊驴,侵入性強
參考資料:https://www.cnblogs.com/ludashi/p/5895725.html
參考資料:https://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/