[這是第1篇]
導語:像素對齊并不是一個復雜的問題,但是開發中稍不注意的話,是會造成像素不對齊的情況(恰恰容易被忽視掉),本文使用一個案例來分析如何解決像素不對齊問題。
背景知識:像素對齊
1、基礎
iOS設備上,有邏輯像素(point)和 物理像素(pixel)之分,像素對齊指的是物理像素對齊,對齊就是像素點的值是整數,如某視圖的寬高是100pixel * 100 pixel。
point和pixel的比例是通過[[UIScreen mainScreen] scale]來制定的。在沒有視網膜屏之前,1point = 1pixel;但是2x和3x的視網膜屏出來之后,1point等于2pixel或3pixel。
在UI設計師提供的設計稿標注,和在代碼中設置frame,其中x,y,width,height的單位是 邏輯像素(point);GPU在渲染圖形之前,系統會將邏輯像素(point)換算成 物理像素(pixel)。
2、像素對齊 VS 像素不對齊
邏輯像素(point)乘以2(2x的視網膜屏) 或3(3x的視網膜屏)得到整數值,或者說得到的浮點數且小數點后都是0的,這就像素對齊了,否則就是像素不對齊。
出現像素不對齊的情況,會導致在GPU渲染時,對沒對齊的邊緣,需要進行插值計算,這個插值計算的過程會有性能損耗。
3、發現像素不對齊
在模擬器上提供了Debug -->Color Misaligned Images選項可以把像素不對齊的部分顯示出來;也可以使用Core Animation中Display Settings中的Color Misaligned Images選項將像素不對齊的部分顯示出來
當UIView(及其子類)的frame像素不對齊顯示洋紅色;當圖片的像素大小與控件的大小不一致而導致需要縮放時,顯示黃色。
因為項目中大量使用UITableView來構建UI界面【詳細參考iOS實錄1:使用UITableView構建UI界面】。下面就QSUseTableViewDemo中的詳情頁來對比優化前后的效果。優化前,開啟模擬器上的Debug -->Color Misaligned Images選項,發現:文本部分出現洋紅色(frame像素不對齊)和 圖片部分是黃色(圖片的縮放導致的不對齊)。
一、文本計算的坑
1、存在的問題
理論上設置View的大小,最好預先設置好,盡量不要計算。但是項目中,很多時候需要先計算出文本在某字體下的寬高,再設置view的frame。,有時候文本計算得到的width和height是小數,如16.48、15.32。如果直接使用,必然會造成像素不對齊的問題(因為16.48、15.32乘以2或3得到的都不是整數)。
2、解決辦法
我們在項目擴展了NSString方法,使用新增的方法統一計算文本的大小,在這些方法中使用ceil()將小數點后數據除去,使得計算的結果小數點后都是0
//單行的
- (CGSize)textSizeWithFont:(UIFont*)font{
CGSize textSize = [self sizeWithAttributes:@{NSFontAttributeName:font}];
textSize = CGSizeMake((int)ceil(textSize.width), (int)ceil(textSize.height));
return textSize;
}
/**
根據字體、行數、行間距和constrainedWidth計算多行文本占據的size
**/
- (CGSize)textSizeWithFont:(UIFont*)font
numberOfLines:(NSInteger)numberOfLines
lineSpacing:(CGFloat)lineSpacing
constrainedWidth:(CGFloat)constrainedWidth
isLimitedToLines:(BOOL *)isLimitedToLines{
if (self.length == 0) {
return CGSizeZero;
}
CGFloat oneLineHeight = font.lineHeight;
CGSize textSize = [self boundingRectWithSize:CGSizeMake(constrainedWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:font} context:nil].size;
CGFloat rows = textSize.height / oneLineHeight;
CGFloat realHeight = oneLineHeight;
// 0 不限制行數
if (numberOfLines == 0) {
if (rows >= 1) {
realHeight = (rows * oneLineHeight) + (rows - 1) * lineSpacing;
}
}else{
if (rows > numberOfLines) {
rows = numberOfLines;
if (isLimitedToLines) {
*isLimitedToLines = YES; //被限制
}
}
realHeight = (rows * oneLineHeight) + (rows - 1) * lineSpacing;
}
return CGSizeMake(ceil(constrainedWidth),ceil(realHeight));
}
@end
二、UITableview的header和footer高度的坑
1、存在的問題
項目中使用Group Style的UITableview,為了避免讓系統去設置header或者footer的高度,我們自己去設置tableView:heightForHeaderInSection: tableView:heightForFooterInSection的值,早前做法是直接將其返回0.01f,達到隱藏header和footer的效果,但是這么做是會造成像素不對齊。
2、解決辦法
使用盡可能下的數值,0.01還不夠小,直接使用系統提供的CGFLOAT_MIN吧。
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
return CGFLOAT_MIN;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
return CGFLOAT_MIN;
}
注意:在設置UITableViewCell的高度時候,使用的浮點數,小數點后不可以有0的數,否則造成像素不對齊。
3、解決效果
注明:經過第一和第二步的優化,文本像素不對齊的問題解決了。
三、圖片像素不對齊的情況
1、存在的問題
圖片的size和顯示圖片的imageView的size(邏輯像素(point))不相等。
2、解決辦法
圖片分為兩種,本地圖片和網絡上下載的圖片,前者是UI提供的,存在項目中,這就要求**UI設計師同事提供@2x和@3x圖片,因為@2x的圖片在@3x的屏幕上也會發生像素不對齊的問題;而網絡上獲取的圖片沒有@2x和@3x的區別,需要我們縮放圖片到與UIImageView對應的尺寸,且縮放后的圖片的scale和[UIScreen mainScreen].scale相等,再顯示出來。
1)圖片縮放的方法(分類新增UIImage的縮放方法)
- (UIImage *)scaleImageWithSize:(CGSize)size{
if (CGSizeEqualToSize(size, self.size)) {
return self;
}
//創建上下文
UIGraphicsBeginImageContextWithOptions(size, YES, [UIScreen mainScreen].scale);
//繪圖
[self drawInRect:CGRectMake(0, 0, size.width, size.height)];
//獲取新圖片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
2)圖片縮放在非主線程,更新圖片在主線程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//壓縮背景圖片 & 頭像圖片
UIImage *bgImage = [[UIImage imageNamed:self.cellModel.bgImageName] scaleImageWithSize:_bgImageView.frame.size];
UIImage *image = [[UIImage imageNamed:self.cellModel.iconImageName] scaleImageWithSize:_iconImageView.frame.size];
dispatch_sync(dispatch_get_main_queue(), ^{
_bgImageView.image = bgImage;
_iconImageView.image = image;
_iconImageView.hidden = (image != nil) ? NO : YES;
});
});
注明:圖片的縮放是相對耗時的,不應該放在UI線程(主線程),否則影響UI的流程體驗;這里使用加載本地圖片,模擬從網絡獲取圖片。一般項目中使用SDWebImage來下載網絡圖片,為了更好處理圖片的縮放和圓角等問題,需要在原來庫增加某些特性(圖片縮放、裁圓角和緩存等),這個后面再說。
3、解決效果
經過第三步的優化,圖片不對齊的問題(黃色區域沒有了)被解決。
總結:解決像素不對齊的基本準則
1、frame設置時候,使用整數; 需要計算frame時候,計算的結果使用ceil處理一下,避免小數點后有非0數存在。UITableViewCell的高度的高度是整數。
2、項目中,要求UI設計師提供@2x和@3x的切圖。
3、設置imageView的size要和切圖的size(邏輯像素(point))相等。
4、網絡上獲取的圖片的size要縮放和imageView的size(邏輯像素(point))要相等,縮放后的圖片的scale和[UIScreen mainScreen].scale要相等。解決方案參考iOS實錄17:網絡圖片的優化顯示
5、縮放這樣的耗時操作應該放到子線程去做。最好做緩存,避免每次顯示都需要縮放操作。
源代碼直通車:QSUseTableViewDemo