iOS 图片渐进式下载

为了省去制作图片的麻烦,我就直接拿YYWebImage里面的图片了,我个人也是建议使用这个图片框架来做渐进式下载。

先看下YYKit中做的效果图。
渐进式图片

图片加载很美观,用户体验性非常棒。当我第一次看到的时候,就兴奋的直接拿着代码去用,但是发现并不行,没有效果。后来查了资料才知道这种下载是有要求的。大家若测试,可以用我下面这个代码URL。

 NSURL *url = [NSURL URLWithString:@"http://og3u5glro.bkt.clouddn.com/%E6%B8%90%E8%BF%9B%E5%BC%8F%E5%9B%BE%E7%89%87.jpg"];
 [self.iconImageView yy_setImageWithURL:url
                                   options:YYWebImageOptionProgressiveBlur];

但是,仅仅吃人家给的鱼干,远远满足不了我等对学习的渴望。于是,在和一个群友讨论后,决定自己写出这样的一个效果。

下面我先简单解释下图片格式
然后我会贴上自己实现这个渐进式下载的代码思路
最后当然是对你们来说最关心的,在文末我将提供一个简单的Demo

言归正传,先来解释下,为什么你从百度随便弄一张图的链接放上去,但没有渐进式的下载效果。

实际上这和图片的格式支持有关。
一般来说我们碰到的图片大多都是Baseline JPEG这种格式的图片,我们也称为标准格式。然而渐进式图片下载,则需要Progressive JPEG这个格式,称之为渐进式,当然还有Interlaced格式等等,说到这里也许你已经明白了,是图片格式本身就不支持,不是代码没作用!下面说下这两个格式。

Baseline JPEG

标准格式的图片,是从上往下的方式逐行进行扫描。也就是看起来是一行一行的下载绘制,细心的同学会发现,YYWebImage里面就有这样的下载设置,代码如下:

[imageView yy_setImageWithURL:url options:YYWebImageOptionProgressive];

那看起来大概是这样的


上下扫描下载
Progressive JPEG

渐进式格式图片的扫描是多次的,再打开图片的过程中,先显示他的轮廓,在慢慢扫描绘制所有的色块,最终清晰。效果最上面大家已经看过了,这种技术被广泛应用于大图的下载显示上。
渐进式图片的一些小缺点:最初绘制的模糊图片,实际上与原图的大小有相差、这种绘制更加消耗CPU...

那么,这种图片如何制作呢?

很简单,在photoshop中有存储为web所用格式,打开后选择连续就是Progressive JPEG

这样美观的渐进式下载,我如何实现呢?

图片解码需要用到这个框架处理

#import <ImageIO/ImageIO.h>

首先使用CGImageSourceCreateIncremental(NULL)创建图片源,然后在网络请求代理中拼接每次返回的图片data,使用CGImageSourceUpdateData更新图片数据,最后使用CGImageSourceCreateImageAtIndex来创建图片显示

其实过程就这么多,ok,贴上主要代码,运行看看效果。

@interface ViewController ()<NSURLSessionDelegate> {
    
    NSMutableData * _recieveData;//当前下载date
    long long _expectedLeght;//预估大小
    CGImageSourceRef _incrementallyImgSource;
}
@property(nonatomic, strong) UIImageView *imageView;
/**
 渐进式加载图片
 */
- (void)loadImage {
    
    _incrementallyImgSource = CGImageSourceCreateIncremental(NULL);
    
    _recieveData = [[NSMutableData alloc] init];
    
    [self.view addSubview:self.imageView];
    
    NSURL *url = [NSURL URLWithString:@"http://og3u5glro.bkt.clouddn.com/%E6%B8%90%E8%BF%9B%E5%BC%8F%E5%9B%BE%E7%89%87.jpg"];
    
    NSURLSession *session=[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    
    NSURLRequest *request=[NSURLRequest requestWithURL:url];
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
    [task resume];
    
}

// 1.接收到服务器的响应
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
    // 允许处理服务器的响应,才会继续接收服务器返回的数据
    completionHandler(NSURLSessionResponseAllow);
    
    _expectedLeght = response.expectedContentLength;
    NSLog(@"_expectedLeght   %lld",_expectedLeght);
    
}

// 2.接收到服务器的数据(可能调用多次)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    
    [data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
        [_recieveData appendBytes:bytes length:byteRange.length];
    }];
    
    BOOL isloadFinish = NO;
    if (_expectedLeght == _recieveData.length) {
        isloadFinish = YES;
    }
   
    CGImageSourceUpdateData(_incrementallyImgSource, (CFDataRef)_recieveData, isloadFinish);
    CGImageRef imageRef = CGImageSourceCreateImageAtIndex(_incrementallyImgSource, 0, NULL);
    UIImage *imageaa = [UIImage imageWithCGImage:imageRef];

    self.imageView.image = imageaa;
    CGImageRelease(imageRef);
    
    NSLog(@"_recieveData  %lu",(unsigned long)_recieveData.length);
    
}

效果图

yscrollsss.gif

可以看到,一个明显的下拉效果,仔细看确实是有了模糊到清晰的过程。可是这个下拉似乎不怎么好看啊,而且这个模糊效果真心不好,好像是太清晰了,这不是我们需要的优美的体验。

那么,模糊效果不好怎么处理呢?我第一时间想到的就是,我要再加一层毛玻璃!以此获得更好的用户体验。

毛玻璃处理应该是个动态的,请看下面代码

//毛玻璃处理
- (UIImage *)filterWith:(UIImage *)image andRadius:(CGFloat)radius {
    
    CIImage *inputImage = [[CIImage alloc] initWithCGImage:image.CGImage];
    
    CIFilter *affineClampFilter = [CIFilter filterWithName:@"CIAffineClamp"];
    CGAffineTransform xform = CGAffineTransformMakeScale(1.0, 1.0);
    [affineClampFilter setValue:inputImage forKey:kCIInputImageKey];
    [affineClampFilter setValue:[NSValue valueWithBytes:&xform
                                               objCType:@encode(CGAffineTransform)]
                         forKey:@"inputTransform"];
    
    CIImage *extendedImage = [affineClampFilter valueForKey:kCIOutputImageKey];
    
    CIFilter *blurFilter =
    [CIFilter filterWithName:@"CIGaussianBlur"];
    [blurFilter setValue:extendedImage forKey:kCIInputImageKey];
    [blurFilter setValue:@(radius) forKey:@"inputRadius"];
    
    CIImage *result = [blurFilter valueForKey:kCIOutputImageKey];
    
    CIContext *ciContext = [CIContext contextWithOptions:nil];
    
    CGImageRef cgImage = [ciContext createCGImage:result fromRect:inputImage.extent];
    
    UIImage *uiImage = [UIImage imageWithCGImage:cgImage];
    CGImageRelease(cgImage);
    
    return uiImage;
}

方法写好,那么调用看看吧!

// 2.接收到服务器的数据(可能调用多次)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    
    [data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
        [_recieveData appendBytes:bytes length:byteRange.length];
    }];
    
    BOOL isloadFinish = NO;
    if (_expectedLeght == _recieveData.length) {
        isloadFinish = YES;
    }
    //不希望出现下拉效果 同时避免数据太少毛玻璃绘制crash
//    if (_recieveData.length <= _expectedLeght*0.12) {
//        return;
//    }
    CGImageSourceUpdateData(_incrementallyImgSource, (CFDataRef)_recieveData, isloadFinish);
    CGImageRef imageRef = CGImageSourceCreateImageAtIndex(_incrementallyImgSource, 0, NULL);
    UIImage *imageaa = [UIImage imageWithCGImage:imageRef];
    
    CGFloat length = _expectedLeght;
    CGFloat dataLength = _recieveData.length;
    NSLog(@"....%f",dataLength/length);
    
    self.imageView.image = [self filterWith:imageaa andRadius:(1-dataLength/length)*10];

    CGImageRelease(imageRef);
    
    NSLog(@"_recieveData  %lu",(unsigned long)_recieveData.length);
    
}

效果图,迫不及待~
为了看清下载效果,找的图片很大2.8M左右。

progress.gif

Gif制作不是很清晰,实际效果要比这个好很多。
有兴趣的同学可以去我GitHub下载这个Demo

既然说了这个图片下载,还有个动态图加载图片跟大家分享一下。

大致是这样的

animation.gif

在以前,我们都是放一张静态图的,当图片下载完成,更改图片,也就是著名的 PlaceholderImage 。如果我想放一张动态的placeholderImage怎么办?我推荐使用YYImage,实现步骤如下:
第一步:把你的imageView继承 YYAnimatedImageView(似乎不继承也可以,我记不清了)
第二步:

    [self.iconImageView sd_setImageWithURL:[NSURL URLWithString:url] 
    placeholderImage:[YYImage imageNamed:@"placeImage.gif"]];
有一点不得不说:万恶的卡顿!虽然已经优化的不错了,但是仍然出现了偶尔轻微卡顿的情况,可能我有强迫症。总之,这样不能众享丝滑了。如果有老司机有办法一路滑下去,请带带我!

iOS技术交流群:511860085 欢迎加入!

最后放一张Progressive JPEG格式的图片
mew_progressive.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,392评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 2015-12-24 ㈠做的有价值的事情 ①背英语单词,晨跑 ②VFP课程(第三章) ③看视频《大众理财》 ④普通...
    tang小米阅读 802评论 102 5
  • 高歌猛进的票房证实这是一部成功的商业片。 首先,作为塑造英雄的类型片,它确实是态度端正的诚意之作,好莱坞大片中的元...
    夜风如歌阅读 176评论 0 0
  • 夸父逐日,女娲补天;嫦娥奔月,张生煮海。麻姑说,我看见沧海变桑田;窦娥讲,六月大雪为我降人间。《封神演义》里的千里...
    我在万万写字的地方阅读 287评论 0 0