最近在做GPU圖層顯示的優(yōu)化,其中關(guān)于Color Misaligned Images優(yōu)化文章有很多,但在具體優(yōu)化的時(shí)候還是遇到了點(diǎn)的問(wèn)題,特此記錄。
檢測(cè)方式
以下兩種方式均可發(fā)現(xiàn)存在Misaligned Images問(wèn)題的地方:
- 模擬器調(diào)試時(shí),打開模擬器的Debug - Color Misaligned Images菜單選項(xiàng)。最快捷,但僅限模擬器上查看。
- Instrument性能檢測(cè)時(shí),選中Core Animation模板,在Display Settings中勾選Color Misaligned Images選項(xiàng)。可針對(duì)模擬器和真機(jī),可查看真機(jī)上所有應(yīng)用的像素混合情況。
問(wèn)題定義
打開開關(guān)后,看到部分視圖會(huì)有黃色或洋紅色(Magenta)的圖層標(biāo)記,代表其像素不對(duì)齊。
不對(duì)齊:視圖或圖片的點(diǎn)數(shù)(point),不能換算成整數(shù)的像素值(pixel),導(dǎo)致顯示視圖的時(shí)候需要對(duì)沒(méi)對(duì)齊的邊緣進(jìn)行額外混合計(jì)算,影響性能。
洋紅色:UIView的frame像素不對(duì)齊,即不能換算成整數(shù)像素值。
黃色:UIImageView的圖片像素大小與其frame.size不對(duì)齊,圖片發(fā)生了縮放造成。
優(yōu)化方式
frame像素不對(duì)齊
針對(duì)frame像素不對(duì)齊,借助ceilf()、floorf()、CGRectIntegral()等將小數(shù)點(diǎn)后數(shù)據(jù)除去即可。
使用floorf時(shí),需要注意是否會(huì)因?yàn)橄蛳氯≌绊懸晥D的顯示。
關(guān)于CGRectIntegral的使用,《Aligned UIViews》這篇文章中提到一種非常特殊的情況,在layoutSubviews中使用CGRectIntegral來(lái)重新設(shè)置frame,可能導(dǎo)致同一個(gè)view在不同時(shí)候計(jì)算得到到的x和width不同的情況,但實(shí)際測(cè)試并沒(méi)有發(fā)現(xiàn)文章中描述的問(wèn)題。
0.5個(gè)點(diǎn),會(huì)造成像素不對(duì)齊嗎?
在@2x屏幕上不會(huì),但@3x屏幕上會(huì)。會(huì)不會(huì)由最終計(jì)算得像素值是不是整數(shù)判斷,比如上圖中在@3x屏幕上,第4個(gè)label高度為40.1導(dǎo)致了像素不對(duì)齊,但第3個(gè)label高度為40+1/3沒(méi)導(dǎo)致像素不對(duì)齊,在@2x屏幕上當(dāng)然這兩個(gè)寬度都會(huì)導(dǎo)致像素不對(duì)齊。
像素不對(duì)稱齊的元素一般為UILabel或UIImageView。
特別注意,上圖中UILabel寬度不為整數(shù)時(shí)并沒(méi)有有像素不對(duì)齊,但x、y、height不為整數(shù)就會(huì)導(dǎo)致像素不對(duì)齊。
圖片像素不對(duì)齊
上圖的前4個(gè)UIImageView,顯示的是同一張圖片,該圖片@2x像素為128x128px,@3x像素為192x192px,僅當(dāng)UIImageView的size為64x64的時(shí)候才沒(méi)有像素不對(duì)齊。
遇到這種情況需要嚴(yán)格約束Icon圖片和UIImageView的尺寸。
還有種情況即圖片是從服務(wù)端獲取到的,大小不規(guī)則。直接在UIImageView上顯示容易出現(xiàn)像素不對(duì)齊。
解決方法:將下載到的圖片,縮放到與UIImageView對(duì)應(yīng)的尺寸,再顯示出來(lái)。
多種圖片縮放方式及其性能比較可參考《Image Resizing Techniques》,此處提供一個(gè)簡(jiǎn)單實(shí)現(xiàn):
@implementation UIImage(Resize)
/**
將UIImage縮放到指定大小
@param boxSize 一般為UIImageView的size
@return 縮放后的UIImage
*/
- (UIImage *)imageShowInSize:(CGSize)boxSize {
if (CGSizeEqualToSize(boxSize, self.size)) {
return self;
}
CGFloat screenScale = [[UIScreen mainScreen] scale];
CGFloat rate = MAX(boxSize.width / self.size.width, boxSize.height / self.size.height);
CGSize resize = CGSizeMake(self.size.width * rate , self.size.height * rate );
CGRect drawRect = CGRectMake(-(resize.width - boxSize.width) / 2.0 ,
-(resize.height - boxSize.height) / 2.0 ,
resize.width,
resize .height);
boxSize = CGSizeMake(boxSize.width, boxSize.height);
UIGraphicsBeginImageContextWithOptions(boxSize, YES, screenScale);
[self drawInRect:drawRect];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
@end
注意:
- 具體使用時(shí),可能需要根據(jù)UIImageView的contentMode屬性調(diào)整縮放方式。
- 該方法執(zhí)行會(huì)花費(fèi)一定的時(shí)間,在列表上顯示需要縮放的圖片,為了不影響列表滾動(dòng)流程體驗(yàn),該操作應(yīng)放到非主線進(jìn)行,并考慮將縮放后的結(jié)果緩存以便下次直接使用。
- 根據(jù)原始圖片尺寸大小,當(dāng)前狀況是否明顯影響列表滾動(dòng)等具體情況再?zèng)Q定是否優(yōu)化,比如目前微博首頁(yè)的用戶頭像和九宮格圖片不存在像素不對(duì)齊情況,而微信朋友圈的用戶頭像和圖片是染成黃色的像素不對(duì)齊。
UITableview上UILabel的不對(duì)齊
在某些UITableview上,會(huì)發(fā)現(xiàn)盡管UILabel的frame已經(jīng)取整了,但所有Cell上Label還是全都被染成了紅色,非常不解。
打開Xcode的Debug View Hierarchy,可以看到進(jìn)入頁(yè)面UITableview還沒(méi)做任何滾動(dòng)時(shí),UILabel的frame沒(méi)有異常,但是UITableViewCell的y坐標(biāo)不是整數(shù),有個(gè)0.01的差值。
這次恍然大悟,父視圖的像素不對(duì)齊也會(huì)影響到子視圖。而此處0.01差值的來(lái)源,是UITableview的header高度。
在使用Group Style的UITableview時(shí),如果tableView:heightForHeaderInSection:回調(diào)返回0,系統(tǒng)會(huì)認(rèn)為沒(méi)有設(shè)置header的高度而重新提供一個(gè)默認(rèn)的header高度,導(dǎo)致在UITableview中看到一個(gè)空白的header。
一種簡(jiǎn)單但有隱患的處理方式,就是在回調(diào)里返回一個(gè)很小的高度,比如0.1、0.01,這樣能達(dá)到隱藏header的效果,但也造成了此處的像素不對(duì)齊問(wèn)題。
解決方法:
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return CGFLOAT_MIN;
}
避免使用0.01這樣的具體數(shù)值(0.01還不足夠小,0.0001就能避免此處的不對(duì)齊),直接使用系統(tǒng)給的CGFLOAT_MIN,這個(gè)足夠小的值既能避免上述情況,又能讓代碼更直觀。
隱藏header的其它方法和原理可參考:0代碼隱藏GroupedTableView上邊多余的間隔。
參考文章: