[這是第1篇]
導(dǎo)語:像素對(duì)齊并不是一個(gè)復(fù)雜的問題,但是開發(fā)中稍不注意的話,是會(huì)造成像素不對(duì)齊的情況(恰恰容易被忽視掉),本文使用一個(gè)案例來分析如何解決像素不對(duì)齊問題。
背景知識(shí):像素對(duì)齊
1、基礎(chǔ)
iOS設(shè)備上,有邏輯像素(point)和 物理像素(pixel)之分,像素對(duì)齊指的是物理像素對(duì)齊,對(duì)齊就是像素點(diǎn)的值是整數(shù),如某視圖的寬高是100pixel * 100 pixel。
point和pixel的比例是通過[[UIScreen mainScreen] scale]來制定的。在沒有視網(wǎng)膜屏之前,1point = 1pixel;但是2x和3x的視網(wǎng)膜屏出來之后,1point等于2pixel或3pixel。
在UI設(shè)計(jì)師提供的設(shè)計(jì)稿標(biāo)注,和在代碼中設(shè)置frame,其中x,y,width,height的單位是 邏輯像素(point);GPU在渲染圖形之前,系統(tǒng)會(huì)將邏輯像素(point)換算成 物理像素(pixel)。
2、像素對(duì)齊 VS 像素不對(duì)齊
邏輯像素(point)乘以2(2x的視網(wǎng)膜屏) 或3(3x的視網(wǎng)膜屏)得到整數(shù)值,或者說得到的浮點(diǎn)數(shù)且小數(shù)點(diǎn)后都是0的,這就像素對(duì)齊了,否則就是像素不對(duì)齊。
出現(xiàn)像素不對(duì)齊的情況,會(huì)導(dǎo)致在GPU渲染時(shí),對(duì)沒對(duì)齊的邊緣,需要進(jìn)行插值計(jì)算,這個(gè)插值計(jì)算的過程會(huì)有性能損耗。
3、發(fā)現(xiàn)像素不對(duì)齊
在模擬器上提供了Debug -->Color Misaligned Images選項(xiàng)可以把像素不對(duì)齊的部分顯示出來;也可以使用Core Animation中Display Settings中的Color Misaligned Images選項(xiàng)將像素不對(duì)齊的部分顯示出來
當(dāng)UIView(及其子類)的frame像素不對(duì)齊顯示洋紅色;當(dāng)圖片的像素大小與控件的大小不一致而導(dǎo)致需要縮放時(shí),顯示黃色。
因?yàn)轫?xiàng)目中大量使用UITableView來構(gòu)建UI界面【詳細(xì)參考iOS實(shí)錄1:使用UITableView構(gòu)建UI界面】。下面就QSUseTableViewDemo中的詳情頁來對(duì)比優(yōu)化前后的效果。優(yōu)化前,開啟模擬器上的Debug -->Color Misaligned Images選項(xiàng),發(fā)現(xiàn):文本部分出現(xiàn)洋紅色(frame像素不對(duì)齊)和 圖片部分是黃色(圖片的縮放導(dǎo)致的不對(duì)齊)。

一、文本計(jì)算的坑
1、存在的問題
理論上設(shè)置View的大小,最好預(yù)先設(shè)置好,盡量不要計(jì)算。但是項(xiàng)目中,很多時(shí)候需要先計(jì)算出文本在某字體下的寬高,再設(shè)置view的frame。,有時(shí)候文本計(jì)算得到的width和height是小數(shù),如16.48、15.32。如果直接使用,必然會(huì)造成像素不對(duì)齊的問題(因?yàn)?6.48、15.32乘以2或3得到的都不是整數(shù))。
2、解決辦法
我們?cè)陧?xiàng)目擴(kuò)展了NSString方法,使用新增的方法統(tǒng)一計(jì)算文本的大小,在這些方法中使用ceil()將小數(shù)點(diǎn)后數(shù)據(jù)除去,使得計(jì)算的結(jié)果小數(shù)點(diǎn)后都是0
//單行的
- (CGSize)textSizeWithFont:(UIFont*)font{
CGSize textSize = [self sizeWithAttributes:@{NSFontAttributeName:font}];
textSize = CGSizeMake((int)ceil(textSize.width), (int)ceil(textSize.height));
return textSize;
}
/**
根據(jù)字體、行數(shù)、行間距和constrainedWidth計(jì)算多行文本占據(jù)的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 不限制行數(shù)
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、存在的問題
項(xiàng)目中使用Group Style的UITableview,為了避免讓系統(tǒng)去設(shè)置header或者footer的高度,我們自己去設(shè)置tableView:heightForHeaderInSection: tableView:heightForFooterInSection的值,早前做法是直接將其返回0.01f,達(dá)到隱藏header和footer的效果,但是這么做是會(huì)造成像素不對(duì)齊。
2、解決辦法
使用盡可能下的數(shù)值,0.01還不夠小,直接使用系統(tǒng)提供的CGFLOAT_MIN吧。
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
return CGFLOAT_MIN;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
return CGFLOAT_MIN;
}
注意:在設(shè)置UITableViewCell的高度時(shí)候,使用的浮點(diǎn)數(shù),小數(shù)點(diǎn)后不可以有0的數(shù),否則造成像素不對(duì)齊。
3、解決效果
注明:經(jīng)過第一和第二步的優(yōu)化,文本像素不對(duì)齊的問題解決了。

三、圖片像素不對(duì)齊的情況
1、存在的問題
圖片的size和顯示圖片的imageView的size(邏輯像素(point))不相等。
2、解決辦法
圖片分為兩種,本地圖片和網(wǎng)絡(luò)上下載的圖片,前者是UI提供的,存在項(xiàng)目中,這就要求**UI設(shè)計(jì)師同事提供@2x和@3x圖片,因?yàn)锧2x的圖片在@3x的屏幕上也會(huì)發(fā)生像素不對(duì)齊的問題;而網(wǎng)絡(luò)上獲取的圖片沒有@2x和@3x的區(qū)別,需要我們縮放圖片到與UIImageView對(duì)應(yīng)的尺寸,且縮放后的圖片的scale和[UIScreen mainScreen].scale相等,再顯示出來。
1)圖片縮放的方法(分類新增UIImage的縮放方法)
- (UIImage *)scaleImageWithSize:(CGSize)size{
if (CGSizeEqualToSize(size, self.size)) {
return self;
}
//創(chuàng)建上下文
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;
});
});
注明:圖片的縮放是相對(duì)耗時(shí)的,不應(yīng)該放在UI線程(主線程),否則影響UI的流程體驗(yàn);這里使用加載本地圖片,模擬從網(wǎng)絡(luò)獲取圖片。一般項(xiàng)目中使用SDWebImage來下載網(wǎng)絡(luò)圖片,為了更好處理圖片的縮放和圓角等問題,需要在原來庫增加某些特性(圖片縮放、裁圓角和緩存等),這個(gè)后面再說。
3、解決效果
經(jīng)過第三步的優(yōu)化,圖片不對(duì)齊的問題(黃色區(qū)域沒有了)被解決。

總結(jié):解決像素不對(duì)齊的基本準(zhǔn)則
1、frame設(shè)置時(shí)候,使用整數(shù); 需要計(jì)算frame時(shí)候,計(jì)算的結(jié)果使用ceil處理一下,避免小數(shù)點(diǎn)后有非0數(shù)存在。UITableViewCell的高度的高度是整數(shù)。
2、項(xiàng)目中,要求UI設(shè)計(jì)師提供@2x和@3x的切圖。
3、設(shè)置imageView的size要和切圖的size(邏輯像素(point))相等。
4、網(wǎng)絡(luò)上獲取的圖片的size要縮放和imageView的size(邏輯像素(point))要相等,縮放后的圖片的scale和[UIScreen mainScreen].scale要相等。解決方案參考iOS實(shí)錄17:網(wǎng)絡(luò)圖片的優(yōu)化顯示
5、縮放這樣的耗時(shí)操作應(yīng)該放到子線程去做。最好做緩存,避免每次顯示都需要縮放操作。
源代碼直通車:QSUseTableViewDemo