如何讓ios的界面流暢不卡頓
近些年,App 越來越推崇體驗(yàn)至上,隨隨便便亂寫一通的話已經(jīng)很難讓用戶買帳了,順滑的列表便是其中很重要的一點(diǎn)。如果一個 App 的頁面滾動起來總是卡頓卡頓的,輕則被當(dāng)作反面教材來吐槽或者襯托“我們的 App balabala.。?!?,重則直接卸載。正好最近在優(yōu)化這一塊兒,總結(jié)記錄下。
如果說有什么好的博客文章推薦,ibireme 的 iOS 保持界面流暢的技巧 這篇堪稱業(yè)界毒瘤,墻裂推薦反復(fù)閱讀。這篇文章中講解了很多的優(yōu)化點(diǎn),我自己總結(jié)了下收益最大的兩個優(yōu)化點(diǎn):
避免重復(fù)多次計(jì)算 cell 行高
文本異步渲染
大家可以看看上面這張圖的對比分析,數(shù)據(jù)是 iPhone6 的機(jī)子用 instruments 抓的,左邊的是用 Auto Layout 繪制界面的數(shù)據(jù)分析,正常如果想平滑滾動的話,fps 至少需要穩(wěn)定在 55 左右,我們可以發(fā)現(xiàn),在沒有緩存行高和異步渲染的情況下 fps 是最低的,可以說是比較卡頓了,至少是能肉眼感覺出來,能滿足平滑滾動要求的也只有在緩存行高且異步渲染的情況下;右邊的是沒用 Auto Layout 直接用 frame 來繪制界面的數(shù)據(jù)分析,可以發(fā)現(xiàn)即使沒有異步渲染,也能勉強(qiáng)滿足平滑滾動的要求,如果開啟異步渲染的話,可以說是相當(dāng)?shù)慕z滑了。
避免重復(fù)多次計(jì)算 cell 行高
TableView 行高計(jì)算可以說是個老生常談的問題了,heightForRowAtIndexPath: 是個調(diào)用相當(dāng)頻繁的方法,在里面做過多的事情難免會造成卡頓。 在 iOS 8 中,我們可以通過設(shè)置下面兩個屬性來很輕松的實(shí)現(xiàn)高度自適應(yīng):
1
2
self.tableView.estimatedRowHeight = 88;
self.tableView.rowHeight = UITableViewAutomaticDimension;
雖然很方便,不過如果你的頁面對性能有一定要求,建議不要這么做,具體可以看看 sunnyxx 的 優(yōu)化UITableViewCell高度計(jì)算的那些事。文中針對 Auto Layout,提供了個 cell 行高的緩存庫 UITableView-FDTemplateLayoutCell,可以很好的幫助我們避免 cell 行高多次計(jì)算的問題。
如果不使用 Auto Layout,我們可以在請求完拿到數(shù)據(jù)后提前計(jì)算好頁面每個控件的 frame 和 cell 高度,并且緩存在內(nèi)存中,用的時候直接在 heightForRowAtIndexPath: 取出計(jì)算好的值就行,大概流程如下:
模擬請求數(shù)據(jù)回調(diào):
- (void)viewDidLoad {
[ super viewDidLoad];
[self buildTestDataThen:^(NSMutableArray 《fdfeedentity *》 *entities) {
self.data = @[].mutableCopy;
@autoreleasepool {
for (FDFeedEntity *entity in entities) {
FrameModel *frameModel = [FrameModel new ];
frameModel.entity = entity;
[self.data addObject:frameModel];
}
}
[self.tvFeed reloadData];
}];
}《/fdfeedentity *》
一個簡單計(jì)算 frame 、cell 行高方式:
//FrameModel.h
@interface FrameModel : NSObject
@property (assign, nonatomic, readonly) CGRect titleFrame;
@property (assign, nonatomic, readonly) CGFloat cellHeight;
@property (strong, nonatomic) FDFeedEntity *entity;
@end
//FrameModel.m
@implementation FrameModel
- (void)setEntity:(FDFeedEntity *)entity {
if (!entity) return ;
_entity = entity;
CGFloat maxLayout = ([UIScreen mainScreen].bounds.size.width - 20.f);
CGFloat bottom = 4.f;
//title
CGFloat titleX = 10.f;
CGFloat titleY = 10.f;
CGSize titleSize = [entity.title boundingRectWithSize:CGSizeMake(maxLayout, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName : Font(16.f)} context:nil].size;
_titleFrame = CGRectMake(titleX, titleY, titleSize.width, titleSize.height);
//cell Height
_cellHeight = (CGRectGetMaxY(_titleFrame) + bottom);
}
@end
行高取值:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
FrameFeedCell *cell = [tableView dequeueReusableCellWithIdentifier:FrameFeedCellIdentifier forIndexPath:indexPath];
FrameModel *frameModel = self.data[indexPath.row];
cell.model = frameModel;
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
FrameModel *frameModel = self.data[indexPath.row];
return frameModel.cellHeight;
}
控件賦值:
- (void)setModel:(FrameModel *)model {
if (!model) return ;
_model = model;
FDFeedEntity *entity = model.entity;
self.titleLabel.frame = model.titleFrame;
self.titleLabel.text = entity.title;
}
優(yōu)缺點(diǎn)
緩存行高方式有現(xiàn)成的庫簡單方便,雖然 UITableView-FDTemplateLayoutCell 已經(jīng)處理的很好了,但是 Auto Layout 對性能還是會有部分消耗;手動計(jì)算 frame 方式所有的位置都需要計(jì)算,比較麻煩,而且在數(shù)據(jù)量很大的情況下,大量的計(jì)算對數(shù)據(jù)展示時間會有部分影響,相應(yīng)的回報就是性能會更好一些。
文本異步渲染
當(dāng)顯示大量文本時,CPU 的壓力會非常大。對此解決方案只有一個,那就是自定義文本控件,用 TextKit 或最底層的 CoreText 對文本異步繪制。盡管這實(shí)現(xiàn)起來非常麻煩,但其帶來的優(yōu)勢也非常大,CoreText 對象創(chuàng)建好后,能直接獲取文本的寬高等信息,避免了多次計(jì)算(調(diào)整 UILabel 大小時算一遍、UILabel 繪制時內(nèi)部再算一遍);CoreText 對象占用內(nèi)存較少,可以緩存下來以備稍后多次渲染。
幸運(yùn)的是,想支持文本異步渲染也有現(xiàn)成的庫 YYText ,下面來講講如何搭配它最大程度滿足我們?nèi)缃z般順滑的需求:
Frame 搭配異步渲染
基本思路和計(jì)算 frame 類似,只不過把系統(tǒng)的 boundingRectWithSize:、 sizeWithAttributes: 換成 YYText 中的方法:
配置 frame model:
//FrameYYModel.h
@interface FrameYYModel : NSObject
@property (assign, nonatomic, readonly) CGRect titleFrame;
@property (strong, nonatomic, readonly) YYTextLayout *titleLayout;
@property (assign, nonatomic, readonly) CGFloat cellHeight;
@property (strong, nonatomic) FDFeedEntity *entity;
@end
//FrameYYModel.m
@implementation FrameYYModel
- (void)setEntity:(FDFeedEntity *)entity {
if (!entity) return ;
_entity = entity;
CGFloat maxLayout = ([UIScreen mainScreen].bounds.size.width - 20.f);
CGFloat space = 10.f;
CGFloat bottom = 4.f;
//title
NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithString:entity.title];
title.yy_font = Font(16.f);
title.yy_color = [UIColor blackColor];
YYTextContainer *titleContainer = [YYTextContainer containerWithSize:CGSizeMake(maxLayout, CGFLOAT_MAX)];
_titleLayout = [YYTextLayout layoutWithContainer:titleContainer text:title];
CGFloat titleX = 10.f;
CGFloat titleY = 10.f;
CGSize titleSize = _titleLayout.textBoundingSize;
_titleFrame = (CGRect){titleX,titleY,CGSizeMake(titleSize.width, titleSize.height)};
//cell Height
_cellHeight = (CGRectGetMaxY(_titleFrame) + bottom);
}
@end
對比上面 frame,可以發(fā)現(xiàn)多了個 YYTextLayout 屬性,這個屬性可以提前配置文本的特性,包括 font、textColor 以及行數(shù)、行間距、內(nèi)間距等等,好處就是可以把一些邏輯提前處理好,比如根據(jù)接口字段,動態(tài)配置字體顏色,字號等,如果用 Auto Layout,這部分邏輯則不可避免的需要寫在 cellForRowAtIndexPath: 方法中。
UITableViewCell 處理 :
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [ super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (!self) return nil;
YYLabel *title = [YYLabel new ];
title.displaysAsynchronously = YES; //開啟異步渲染
title.ignoreCommonProperties = YES; //忽略屬性
title.layer.borderColor = [UIColor brownColor].CGColor;
title.layer.cornerRadius = 1.f;
title.layer.borderWidth = 1.f;
[self.contentView addSubview:_titleLabel = title];
return self;
}
賦值:
- (void)setModel:(FrameYYModel *)model {
if (!model) return ;
_model = model;
self.titleLabel.frame = model.titleFrame;
self.titleLabel.textLayout = model.titleLayout; //直接取 YYTextLayout
}
Auto Layout 搭配異步渲染
YYText 非常友好,同樣支持 xib,YYText 繼承自 UIView,正常的在 xib 中配置約束就行了,需要注意的一點(diǎn)是,多行文本的情況下需要設(shè)置最大換行寬:
CGFloat maxLayout = [UIScreen mainScreen].bounds.size.width - 20.f;
self.titleLabel.preferredMaxLayoutWidth = maxLayout;
self.subTitleLabel.preferredMaxLayoutWidth = maxLayout;
self.contentLabel.preferredMaxLayoutWidth = maxLayout;
優(yōu)缺點(diǎn)
YYText 的異步渲染能極大程度的提高列表流暢度,真正達(dá)到如絲般順滑,但是在開啟異步時,刷新列表會有閃爍情況,不知道算不算 bug,最近也看到作者回歸了,相信這個庫會越來越好,畢竟 真●大神!
其它
列表中如果存在很多系統(tǒng)設(shè)置的圓角頁面導(dǎo)致卡頓:
label.layer.cornerRadius = 5.f;
label.clipsToBounds = YES;
其實(shí)據(jù)我觀察,只要當(dāng)前屏幕內(nèi)只要設(shè)置圓角的控件個數(shù)不要太多(大概十幾個算個臨界點(diǎn)),就不會引起卡頓。
還有就是只要不設(shè)置 clipsToBounds 不管多少個,都不會卡頓,比如你需要圓角的控件是白色背景色的,然后它的父控件也是白色背景色的,而且沒有點(diǎn)擊后高亮的,就沒必要 clipsToBounds 了。
總結(jié)
YYText 和 UITableView-FDTemplateLayoutCell 搭配可以很大程度的提高列表流暢度,如果時間比較緊迫,可以直接采取 Auto Layout + UITableView-FDTemplateLayoutCell + YYText 方式;如果列表中文本不包含富文本,僅僅顯示文字,又不想引入這兩個庫,可以使用系統(tǒng)方式提前計(jì)算 Frame;如果想最大程度的流暢度,就需要使用 提前計(jì)算 Frame + YYText,具體大家根據(jù)自己情況選擇合適的方案就行。
最后附上 Demo
非常好我支持^.^
(0) 0%
不好我反對
(0) 0%
下載地址
如何讓ios的界面流暢不卡頓下載
相關(guān)電子資料下載
- iOS17.1可能明天發(fā)布,iOS17.1主要修復(fù)哪些問題? 376
- 華為全新鴻蒙蓄勢待發(fā) 僅支持鴻蒙內(nèi)核和鴻蒙系統(tǒng)應(yīng)用 719
- 蘋果手機(jī)系統(tǒng)iOS 17遭用戶質(zhì)疑 731
- iPhone12輻射超標(biāo)?蘋果推送iOS 17.1解決此事 750
- 傳華為囤積零部件 目標(biāo)明年智能手機(jī)出貨7000萬部;消息稱 MiOS 僅限國內(nèi),小米 28208
- 蘋果推送iOS17.0.3,解決iPhone15Pro系列存在機(jī)身過熱 216
- Testin云測兼容和真機(jī)服務(wù)平臺中上線iPhone 15系列手機(jī) 208
- 利爾達(dá)推出搭載HooRiiOS的Matter模組 145
- 運(yùn)放參數(shù)解析:輸入偏置電流(Ibias)和失調(diào)電流(Ios) 128
- 昆侖太科發(fā)布支持國產(chǎn)飛騰騰銳D2000芯片的開源BIOS固件版本 448