在iOS开发过程中经常需要通过网络请求加载图片,有时,需要在创建UIImageView或UIButton来显示图片之前需要提前知道图片的尺寸,根据图片尺寸创建对应大小的控件。但是对于网络图片来说,要想通过最优的方法获得尺寸就略微有点困难,大体思路有这么几种:
1.通过服务器处理。即在下行图片路径时拼接该图片的宽高。这种方法最简单,避免了不必要的网络请求,只需要从URL中截取即可(需要前后台的配合);
2.网络请求。如果有使用SDWebImage,则首先检查是否缓存过该图片,如果没有,先通过文件头获取图片大小(针对格式为png、gif、jpg文件获取其尺寸大小),如果获取失败,则下载完整的图片data,然后计算大小,如果有使用SDWebImage,则使用SDWebImage缓存该图片。
(方法二的最大的缺点就是虽然获取头文件耗时比下载图片快很多,但是还是有延迟,会阻塞主线程UI的绘制,如果网速不好的话会影响用户体验;但是如果使用了SDWebImage缓存,只会在初次加载图片的时候卡顿而已。)
代码如下:
//一个项目里面可能有好几个类都需要实现单例模式。为了更高效的编码,可以利用c语言中宏定义来实现。
//首先宏定义一个单例实现(在.pch/Header文件中拷贝如下代码加入即可)
//这里假设了实例的分享方法叫 shared"className"
//因为方法名 shared"className"是连在一起的,为了让宏能够正确替换掉签名中的“className”需要在前面加上 ##
//当宏的定义超过一行时,在末尾加上“\”表示下一行也在宏定义范围内。
//注意最后一行不需要加"\”。
// @interface
#define singleton_interface(className) \
+ (className *)shared##className;
// @implementation
#define singleton_implementation(className) \
static className *_instance; \
+ (id)allocWithZone:(NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [super allocWithZone:zone]; \
}); \
return _instance; \
} \
+ (className *)shared##className \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [[self alloc] init]; \
}); \
return _instance; \
}
********************************************************************
然后头文件如下
#import 《Foundation/Foundation.h》//特殊字符限制,将书名号改为尖括号
#import "Singleton.h"
@interface BLImageSize : NSObject
singleton_interface(BLImageSize) //公共的访问单例对象的方法
+(CGSize)downloadImageSizeWithURL:(id)imageURL;
@end
*************************************************************************
最后是.m文件实现
#import "BLImageSize.h"
#import "SDImageCache.h"//如果未使用SDWebImage,则忽略
@implementation BLImageSize
singleton_implementation(BLImageSize)
//传入的参数imageURL可以为NSString类型,也可以为NSURL类型
+(CGSize)getImageSizeWithURL:(id)imageURL
{
NSURL* URL = nil;
if([imageURL isKindOfClass:[NSURL class]]){
URL = imageURL;
}
if([imageURL isKindOfClass:[NSString class]]){
URL = [NSURL URLWithString:imageURL];
}
if(URL == nil) {
return CGSizeZero; // url为空则返回CGSizeZero
}
NSString* absoluteString = URL.absoluteString;
//如果未使用SDWebImage,则忽略;检查是否缓存过该图片
#ifdef dispatch_main_sync_safe
if([[SDImageCache sharedImageCache] diskImageExistsWithKey:absoluteString])
{
UIImage* image = [[SDImageCache sharedImageCache] imageFromMemoryCacheForKey:absoluteString];
if(!image)
{
NSData* data = [[SDImageCache sharedImageCache] performSelector:@selector(diskImageDataBySearchingAllPathsForKey:) withObject:URL.absoluteString];
image = [UIImage imageWithData:data];
}
if(image)
{
return image.size;
}
}
#endif
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL];
NSString* pathExtendsion = [URL.pathExtension lowercaseString];
CGSize size = CGSizeZero;
if([pathExtendsion isEqualToString:@"png"]){
size = [self getPNGImageSizeWithRequest:request];
}
else if([pathExtendsion isEqual:@"gif"])
{
size = [self getGIFImageSizeWithRequest:request];
}
else{
size = [self getJPGImageSizeWithRequest:request];
}
if(CGSizeEqualToSize(CGSizeZero, size))
{
// 如果获取文件头信息失败,发送异步请求请求原图
NSData* data = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:URL] returningResponse:nil error:nil];
UIImage* image = [UIImage imageWithData:data];
if(image)
{
//如果未使用SDWebImage,则忽略;缓存该图片
#ifdef dispatch_main_sync_safe
[[SDImageCache sharedImageCache] storeImage:image recalculateFromImage:YES imageData:data forKey:URL.absoluteString toDisk:YES];
#endif
size = image.size;
}
}
//如果对加载速度及用户体验要求不高的话,可以通过主线程获取图片大小
//会阻塞主线程,慎重使用!!!
if (CGSizeEqualToSize(CGSizeZero, size)) {
//直接获取图片大小
NSData *data = [NSData dataWithContentsOfURL:URL];
UIImage *image = [UIImage imageWithData:data];
size = image.size;
}
return size;
}
// 获取PNG图片的大小
+(CGSize)getPNGImageSizeWithRequest:(NSMutableURLRequest*)request
{
[request setValue:@"bytes=16-23" forHTTPHeaderField:@"Range"];
NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
if(data.length == 8)
{
int w1 = 0, w2 = 0, w3 = 0, w4 = 0;
[data getBytes:&w1 range:NSMakeRange(0, 1)];
[data getBytes:&w2 range:NSMakeRange(1, 1)];
[data getBytes:&w3 range:NSMakeRange(2, 1)];
[data getBytes:&w4 range:NSMakeRange(3, 1)];
int w = (w1 << 24) + (w2 << 16) + (w3 << 8) + w4;
int h1 = 0, h2 = 0, h3 = 0, h4 = 0;
[data getBytes:&h1 range:NSMakeRange(4, 1)];
[data getBytes:&h2 range:NSMakeRange(5, 1)];
[data getBytes:&h3 range:NSMakeRange(6, 1)];
[data getBytes:&h4 range:NSMakeRange(7, 1)];
int h = (h1 << 24) + (h2 << 16) + (h3 << 8) + h4;
return CGSizeMake(w, h);
}
return CGSizeZero;
}
// 获取GIF图片的大小
+(CGSize)getGIFImageSizeWithRequest:(NSMutableURLRequest*)request
{
[request setValue:@"bytes=6-9" forHTTPHeaderField:@"Range"];
NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
if(data.length == 4)
{
short w1 = 0, w2 = 0;
[data getBytes:&w1 range:NSMakeRange(0, 1)];
[data getBytes:&w2 range:NSMakeRange(1, 1)];
short w = w1 + (w2 << 8);
short h1 = 0, h2 = 0;
[data getBytes:&h1 range:NSMakeRange(2, 1)];
[data getBytes:&h2 range:NSMakeRange(3, 1)];
short h = h1 + (h2 << 8);
return CGSizeMake(w, h);
}
return CGSizeZero;
}
// 获取JPG图片的大小
+(CGSize)getJPGImageSizeWithRequest:(NSMutableURLRequest*)request
{
[request setValue:@"bytes=0-209" forHTTPHeaderField:@"Range"];
NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
if ([data length] <= 0x58) {
return CGSizeZero;
}
if ([data length] < 210) {// 肯定只有一个DQT字段
short w1 = 0, w2 = 0;
[data getBytes:&w1 range:NSMakeRange(0x60, 0x1)];
[data getBytes:&w2 range:NSMakeRange(0x61, 0x1)];
short w = (w1 << 8) + w2;
short h1 = 0, h2 = 0;
[data getBytes:&h1 range:NSMakeRange(0x5e, 0x1)];
[data getBytes:&h2 range:NSMakeRange(0x5f, 0x1)];
short h = (h1 << 8) + h2;
return CGSizeMake(w, h);
} else {
short word = 0x0;
[data getBytes:&word range:NSMakeRange(0x15, 0x1)];
if (word == 0xdb) {
[data getBytes:&word range:NSMakeRange(0x5a, 0x1)];
if (word == 0xdb) {// 两个DQT字段
short w1 = 0, w2 = 0;
[data getBytes:&w1 range:NSMakeRange(0xa5, 0x1)];
[data getBytes:&w2 range:NSMakeRange(0xa6, 0x1)];
short w = (w1 << 8) + w2;
short h1 = 0, h2 = 0;
[data getBytes:&h1 range:NSMakeRange(0xa3, 0x1)];
[data getBytes:&h2 range:NSMakeRange(0xa4, 0x1)];
short h = (h1 << 8) + h2;
return CGSizeMake(w, h);
} else {// 一个DQT字段
short w1 = 0, w2 = 0;
[data getBytes:&w1 range:NSMakeRange(0x60, 0x1)];
[data getBytes:&w2 range:NSMakeRange(0x61, 0x1)];
short w = (w1 << 8) + w2;
short h1 = 0, h2 = 0;
[data getBytes:&h1 range:NSMakeRange(0x5e, 0x1)];
[data getBytes:&h2 range:NSMakeRange(0x5f, 0x1)];
short h = (h1 << 8) + h2;
return CGSizeMake(w, h);
}
} else {
return CGSizeZero;
}
}
}
@end
PS:
png和gif格式的图片头文件固定,可以准确找到宽高所在字节,只需要极少流量就可以获得图片大小。但是jpeg的头文件很混乱,我真的想说它根本就不分header和body,绘图软件编辑一次保存一次就添加一个标记码,然后存放宽高的标记码就消失在数据流的某个地方了,因为不知道有多少个绘图软件编辑过,也不知道绘图软件标记码里面的内容格式,所以手动获取jpeg图片的宽高基本不可能了。
不过博主偶然发现了一个方法,大家可以尝试一下:
就是在图片url地址后面拼接参数@100p,利用获取JPG格式图片的方法即可获得JPEG图片的大小了。
if (![imgUrl containsString:@"@"]) {
imgUrl = [imgUrl stringByAppendingString:@"@100p"];
}
参考:
http://bbs.itheima.com/thread-158741-1-1.html
http://www.oschina.net/code/snippet_2248391_53038
http://www.2cto.com/kf/201405/304877.html
http://www.cocoachina.com/bbs/read.php?tid=455783