by Fanxiushu 2015-07-10 转载或引用请注明原作者
iOS开发中,使用UIImageView控件来显示图片,非常简单几句话就能显示一个完整的图片:
UIImageView* img =[[UIImageView alloc] initWithFrame:frame];
iOS开发中,使用UIImageView控件来显示图片,非常简单几句话就能显示一个完整的图片:
UIImageView* img =[[UIImageView alloc] initWithFrame:frame];
[superView addSubView:img];
img.image=[UIImage imageNamed:@"picture.png"];
img.image=[UIImage imageNamed:@"picture.png"];
这是图片在本地的情况,可是如果图片文件在网络上该如何实现呢?
你可以使用 NSData* data = [NSData dataWithContentsOfURL:URL];
然后NSData数据转化到 UIImage,
但是 dataWithContentsOfURL是同步获取数据,如果图片文件太大,会阻塞主线程,造成界面假死。
当然也可以使用别人开发的库,比如 SDWebImage就能很好的下载和缓存网络图片。
可是如果花不了多少精力能自己实现这个功能,我就会尽力自己实现来满足某些项目的要求,
这样看起来简洁好维护,更重要的是通过自己实现更能掌握核心部分。
这样看起来简洁好维护,更重要的是通过自己实现更能掌握核心部分。
自己实现关键是要解决网络同步传输问题。
然后你也许又想到一堆别人开发的网络库,比如AFNetworking,ASIHTTPRequest等等,
其实这些都用不着,直接用苹果原生SDK API就可以了,使用他的 NSURLConnection,
至少iOS的自带的网络API,比起windows平台下的WININET要简洁不少。
设计一个 UIImageView的Category类别类,在这个类别类中增加一个方法,比如setImageWithUrl,
这个方法就是我们需要实现的从网络下载并且显示到 UIImageView控件的方法。
要在这个方法中异步下载网络图片,并且要实时显示下载进度,因此得做个NSURLConnection代理类,管理下载进度。
定义的接口如下:
@interface ImageDownDelegate : NSObject
///
其实这些都用不着,直接用苹果原生SDK API就可以了,使用他的 NSURLConnection,
至少iOS的自带的网络API,比起windows平台下的WININET要简洁不少。
设计一个 UIImageView的Category类别类,在这个类别类中增加一个方法,比如setImageWithUrl,
这个方法就是我们需要实现的从网络下载并且显示到 UIImageView控件的方法。
要在这个方法中异步下载网络图片,并且要实时显示下载进度,因此得做个NSURLConnection代理类,管理下载进度。
定义的接口如下:
@interface ImageDownDelegate : NSObject
///
/
@end
@end
@interface UIImageView (URLImage)
@property(nonatomic,strong)ImageDownDelegate* callback;
-(void) setImageWithUrl:(NSString*)url imgName:(NSString*)imgName progress:(void(^)(int progress, NSError* error)) progress;
@end
ImageDownDelegate 是负责数据下载的接口。
ImageDownDelegate 是负责数据下载的接口。
setImageWithUrl的progress,表示使用BLOCK函数块的方式显示下载进度。
参数imgName的意思占位图片,当网络图片正在下载的时候,
用一个本地图片来暂时代替展现。
ImageDownDelegate私有属性如下:
typedef void (^FUNC)(int progress, NSError* error); 这个就是setImageWithUrl提供的下载进度回调函数
typedef void (^FUNC2)(NSData* data, NSError* error); 这个是完成回调函数
typedef void (^FUNC2)(NSData* data, NSError* error); 这个是完成回调函数
@interface ImageDownDelegate()
/
@property(copy) FUNC func;
@property(copy) FUNC2 completion;
@property(copy) FUNC func;
@property(copy) FUNC2 completion;
@property(nonatomic)UIImageView* imageView; 当下载完成后,设置这个图片控件的Image,让网络图片展示出来
@property(nonatomic)UIButton* imageButton; 这个是UIButton的情况下,下载完网络图片之后展示UIButton的背景。
@property(nonatomic)UIButton* imageButton; 这个是UIButton的情况下,下载完网络图片之后展示UIButton的背景。
@property(nonatomic,strong)NSMutableData* data; 这个是下载的图片数据内容,在didReceiveData 组合所有下载的数据。
@property(nonatomic) long long total_length; 图片数据的总长度,在 didReceiveResponse计算得知
@property(nonatomic) long long curr_length;当前下载的数据长度,在 didReceiveData 计算得知
@property(nonatomic) long long curr_length;当前下载的数据长度,在 didReceiveData 计算得知
@end
ImageDownDelegate 接口实现如下主要几个方法:
这个方法是从网络获取 HTTP回答头信息,从这里可以知道下载的图片长度,因此记录下来,作为下载进度的依据。
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
/真正的数据接收回调函数,在这里应该把接收到的数据组合起来,计算并且调用进度函数,通知下载进度。
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
/图片数据已经完全下载完成,在这里应该设置UIImageView的Image,这个时候,图片就可以展现出来。
-(void)connectionDidFinishLoading:(NSURLConnection *)connection;
///下载过程中出现错误,这里应该通知失败。
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
可以看到 UIImageView (URLImage) 里有个 callback属性, 但是类别类不允许有属性存在,因此采用
objc_setAssociatedObject, objc_getAssociatedObject函数来解决这个问题。
为何需要这个代理类的 callback呢?
因为假设某个UIImageView调用 setImageWithUrl下载某个网络图片,当这个网络图片正在下载过程中,
接着它又调用setImageWithUrl下载另一个网络图片,应该取消第一次的下载,这个callback就起到这个作用。
最后看看 setImageWithUrl的大致实现过程:
-(void) setImageWithUrl:(NSString*)url imgName:(NSString*)imgName progress:(void(^)(int progress, NSError* error)) progress
{
if( self.callback){ ///如果之前有正在下载的数据,则需要移除,否则遇到重新请求网络图片的UIImageView可能出现混乱
这里应该真正取消前一次的图片下载,但是我使用 NSURLProtocol协议缓存数据,所以这里让他继续后台下载并且缓存起来。
self.callback.imageButton = nil;
self.callback.imageView = nil;
// NSLog(@"######***** CCOOOOOOOO ");
}
///这里我使用 NSURLProtocol 协议来缓存所有图片数据,这里的意思先判断缓存是否存在这个URL定位的图片,
{
if( self.callback){ ///如果之前有正在下载的数据,则需要移除,否则遇到重新请求网络图片的UIImageView可能出现混乱
这里应该真正取消前一次的图片下载,但是我使用 NSURLProtocol协议缓存数据,所以这里让他继续后台下载并且缓存起来。
self.callback.imageButton = nil;
self.callback.imageView = nil;
// NSLog(@"######***** CCOOOOOOOO ");
}
///这里我使用 NSURLProtocol 协议来缓存所有图片数据,这里的意思先判断缓存是否存在这个URL定位的图片,
如果存在则从缓存获取图片直接显示。
NSString* path = [SimpleURLProtocolCache getCacheFile:url];
if(path){
self.image = [UIImage imageWithContentsOfFile:path];
return ; //
}
///是否显示占位图
if( imgName ){
self.image = [UIImage imageNamed:imgName];
///
}
NSMutableURLRequest* newRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:10.0];
///
ImageDownDelegate* cbk = [[ImageDownDelegate alloc] init];
cbk.func = progress;
cbk.completion = nil;
cbk.imageView = self;
cbk.imageButton = nil;
self.callback = cbk; /
if(progress){
progress(0, nil); ///
}
设置代理,开始图片的真正下载,下载后会同时被 NSURLProtocol 协议缓存。
NSURLConnection* conn = [NSURLConnection connectionWithRequest:newRequest delegate:cbk];
[conn start];
///
}
if( imgName ){
self.image = [UIImage imageNamed:imgName];
///
}
NSMutableURLRequest* newRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:10.0];
///
ImageDownDelegate* cbk = [[ImageDownDelegate alloc] init];
cbk.func = progress;
cbk.completion = nil;
cbk.imageView = self;
cbk.imageButton = nil;
self.callback = cbk; /
if(progress){
progress(0, nil); ///
}
设置代理,开始图片的真正下载,下载后会同时被 NSURLProtocol 协议缓存。
NSURLConnection* conn = [NSURLConnection connectionWithRequest:newRequest delegate:cbk];
[conn start];
///
}
图片缓存,我使用扩展 NSURLProtocol协议来缓存所有 NSURL的数据,
你也可以自己实现缓存来缓存图片,相信自己实现这个缓存也不复杂的。
这个接口对应的资源下载地址:
http://download.csdn.net/detail/fanxiushu/8886897