iOS界面优化系列之:处理图片缩放问题

在众多阅读类的APP中,比如:




这类比较稍微简单的界面中充斥着大量的的图片,多则6张,少则3-4张。再者像微博这类重型社交的APP,有的cell都已经有9张图片。图片的优化程度对整个界面的流畅度来说都是重中之重。(另外一个也很重要的地方就是文本的异步渲染),后几个系列也会说到。

像这种异步加载图片的开源库有很多:SD_WebImageView YYWebImage Kingfisher PINRemoteImage
开发者如果只是简单的调用一下 sd_setImageWithURL: 图片能够很好的显示出来。假如从网络加载的图片与自己设置的UIImageView的尺寸不一致,就会导致这种结果。

Jietu20170112-131912.png
如果是真机调试 打开InstrumentsCore Animation
Jietu20170112-135233.png
在右侧栏中打开
Jietu20170112-135327.png
如果是模拟器,打开Debug ->Color Misaligned Images

这个选项检查了图片是否被缩放,以及像素是否对齐。被放缩的图片会被标记为黄色,像素不对齐则会标注为紫色。**黄色、紫色越多,性能越差。

解决办法:
1.与后台协商,让后台返回的图片尺寸与客户端UIImageView尺寸一致;
2.自己处理。

下面介绍下如何自己处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
extension UIImage {
    func resizeImage(_ image: UIImage, targetSize: CGSize) -> UIImage? {
            let newRect = CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height).integral
            UIGraphicsBeginImageContextWithOptions(targetSize, false, 0)
            if let context = UIGraphicsGetCurrentContext() {
                context.interpolationQuality = .high
                let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: targetSize.height)
                context.concatenate(flipVertical)
                context.draw(self.cgImage!, in: newRect)
                let newImage = UIImage(cgImage: context.makeImage()!)
                UIGraphicsEndImageContext()
               return newImage
            }
        return nil
    }
}

很简单就是网络下载下来的图片按照自己设置的UIImageView的尺寸来重新draw一个。

1
2
3
4
5
6
7
func setImage<T: UIImageView>(_ imageView: T, URLString: String, targetSize: CGSize){
        imageView.pin_setImage(from: URL.init(string: URLString), completion: {(PINRemoteImageManagerResult) in
            if let image = PINRemoteImageManagerResult.image{
                    imageView.image = image.resizeImage(image, targetSize: targetSize)
            }
        })
    }

重新给UIImageView设置image属性

重新打开Color Misaligned Images功能来检测一下
Jietu20170112-140353.png

嗯! 不错哦,之前的黄色没有了。 本以为就达到了的要求。

但是用InstrumentsTime Profiler
Jietu20170112-140732.png
来检测下处理该函数所需要的时间

Jietu20170112-141255.png

用Core Animation来测下FPS

Jietu20170112-142314.png

虽然已经很流畅了(注:文本用到了异步绘制), 基本在54左右, 但是距离满帧59 - 60还差那么一丢丢。

再来观察给UIImage扩展的这个方法 发现它是在主线程中执行,当快速滑动表格时,会有大量的图片在主线程中重新绘制。这个是造成缺失那几帧的主要原因。

解决办法:那就在子线程总去draw, 在主线程中去显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
extension UIImageView {
func drawImage(_ image: UIImage, targetSize: CGSize) {
DispatchQueue.global().async {
let newRect = CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height).integral
UIGraphicsBeginImageContextWithOptions(targetSize, false, 0)
if let context = UIGraphicsGetCurrentContext() {
context.interpolationQuality = .high
let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: targetSize.height)
context.concatenate(flipVertical)
context.saveGState()
context.draw(image.cgImage!, in: newRect)
context.restoreGState()
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
DispatchQueue.main.async {
if let newImage = newImage{
self.layer.contents = newImage.cgImage
}
}
}
}
}
}

Objective-C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-(void)drawImage:(UIImage *)image targetSize:(CGSize )size{
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
CGRect newRect = CGRectMake(0, 0, size.width, size.height);
CGRectIntegral(newRect);
UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
CGAffineTransform transform = CGAffineTransformMake(1, 0, 0, -1, 0, size.height);
CGContextConcatCTM(context, transform);
CGContextSaveGState(context);
CGContextDrawImage(context, newRect, image.CGImage);
CGContextRestoreGState(context);
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
if (newImage) {
self.layer.contents = (id)(newImage.CGImage);
}
});
});
}

该方法也很简单,在子线程中去重新draw一个UIImage,在主线程中去显示。

调用:

1
2
3
4
5
6
7
8
func setImage<T: UIImageView>(_ imageView: T, URLString: String, targetSize: CGSize){
        imageView.pin_setImage(from: URL.init(string: URLString), completion: {(PINRemoteImageManagerResult) in
            if let image = PINRemoteImageManagerResult.image{
                imageView.drawImage(image, targetSize: targetSize)
//                    imageView.image = image.resizeImage(image, targetSize: targetSize)
            }
        })
    }

重新测下帧数以及Time Profile

Jietu20170112-143643.png
当快速滑动的时候基本上能够达到慢帧的状态

Jietu20170112-143924.png
所用的时间也是非常少的。在16ms以内。