iOS 開發中如何顯示網路圖片
阿新 • • 發佈:2019-02-20
by Fanxiushu 2015-07-10 轉載或引用請註明原作者
iOS開發中,使用UIImageView控制元件來顯示圖片,非常簡單幾句話就能顯示一個完整的圖片:
UIImageView* img =[[UIImageView alloc] initWithFrame:frame]; [superView addSubView:img];
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
/// /////
@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 是負責資料下載的介面。 setImageWithUrl的progress,表示使用BLOCK函式塊的方式顯示下載進度。 引數imgName的意思佔位圖片,當網路圖片正在下載的時候, 用一個本地圖片來暫時代替展現。 ImageDownDelegate私有屬性如下: typedef void (^FUNC)(int progress, NSError* error); 這個就是setImageWithUrl提供的下載進度回撥函式
typedef void (^FUNC2)(NSData* data, NSError* error); 這個是完成回撥函式 @interface ImageDownDelegate() /////
@property(copy) FUNC func;
@property(copy) FUNC2 completion; @property(nonatomic)UIImageView* imageView; 當下載完成後,設定這個圖片控制元件的Image,讓網路圖片展示出來
@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 計算得知 @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定位的圖片, 如果存在則從快取獲取圖片直接顯示。
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];
///////
}
圖片快取,我使用擴充套件 NSURLProtocol協議來快取所有 NSURL的資料, 你也可以自己實現快取來快取圖片,相信自己實現這個快取也不復雜的。
iOS開發中,使用UIImageView控制元件來顯示圖片,非常簡單幾句話就能顯示一個完整的圖片:
UIImageView* img =[[UIImageView alloc] initWithFrame:frame]; [superView addSubView:img];
img.image=[UIImage imageNamed:@"picture.png"]; 這是圖片在本地的情況,可是如果圖片檔案在網路上該如何實現呢? 你可以使用 NSData* data = [NSData dataWithContentsOfURL:URL]; 然後NSData資料轉化到 UIImage, 但是 dataWithContentsOfURL是同步獲取資料,如果圖片檔案太大,會阻塞主執行緒,造成介面假死。
這樣看起來簡潔好維護,更重要的是通過自己實現更能掌握核心部分。 自己實現關鍵是要解決網路同步傳輸問題。 然後你也許又想到一堆別人開發的網路庫,比如AFNetworking,ASIHTTPRequest等等,
其實這些都用不著,直接用蘋果原生SDK API就可以了,使用他的 NSURLConnection,
至少iOS的自帶的網路API,比起windows平臺下的WININET要簡潔不少。
設計一個 UIImageView的Category
這個方法就是我們需要實現的從網路下載並且顯示到 UIImageView控制元件的方法。
要在這個方法中非同步下載網路圖片,並且要實時顯示下載進度,因此得做個NSURLConnection代理類,管理下載進度。
定義的介面如下:
@interface ImageDownDelegate : NSObject
/// /////
@end @interface UIImageView (URLImage) @property(nonatomic,strong)ImageDownDelegate* callback; -(void) setImageWithUrl:(NSString*)url imgName:(NSString*)imgName progress:(void(^)(int progress, NSError* error)) progress;
ImageDownDelegate 是負責資料下載的介面。 setImageWithUrl的progress,表示使用BLOCK函式塊的方式顯示下載進度。 引數imgName的意思佔位圖片,當網路圖片正在下載的時候, 用一個本地圖片來暫時代替展現。 ImageDownDelegate私有屬性如下: typedef void (^FUNC)(int progress, NSError* error); 這個就是setImageWithUrl提供的下載進度回撥函式
typedef void (^FUNC2)(NSData* data, NSError* error); 這個是完成回撥函式 @interface ImageDownDelegate() /////
@property(copy) FUNC func;
@property(copy) FUNC2 completion; @property(nonatomic)UIImageView* imageView; 當下載完成後,設定這個圖片控制元件的Image,讓網路圖片展示出來
@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 計算得知 @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定位的圖片, 如果存在則從快取獲取圖片直接顯示。
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];
///////
}
圖片快取,我使用擴充套件 NSURLProtocol協議來快取所有 NSURL的資料, 你也可以自己實現快取來快取圖片,相信自己實現這個快取也不復雜的。
這個介面對應的資源下載地址: