[这是第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