原文
iOS开发之0行代码加载NSBundle中的@2x与@3x图片
本文只针对通过NSBundle对象的方法 pathForResource 获取本地图片资源遇到的图片名无法自动识别@2x与@3x名称的问题进行测试、总结与分享。
加载本地图片资源的方式一般通过以下两种方法:
第1种:
UIImage *img = [UIImage imageNamed:@"imageName"];
第2种:
UIImage *img = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"imageName" ofType:@"imageType"]];
注:其他方法如NSData等本文不涉及,如需了解请找某哥或某娘,谢谢合作。
假定我们都知道第1种方法适合读取重复使用且占用内存小的图片资源,且能根据当前手机硬件能自动识别“@2x”图或“@3x”图。但如果需要加载不常使用且占用内存很大如上百kb甚至上M的图片资源的时候还使用这方式,内存占用势必会很严重。解决这种加载图片资源占用内存问题首选方案是换到第2种,但传入的资源名必须与“.后缀名”前的名称一致,如果资源名添加了“@2x”或“@3x”,而传入的resource名称带或不带“@2x”或“@3x”标识,结果分别会是怎么样的呢?下面我们来测试一下。
不带“@2x”或“@3x”标识:
带“@2x”或“@3x”标识
显然传入的名称带标识后能正常获取到图片资源。
但现在我就是想能过第2种方法加载本地图片资源能像第1种方法一样,不需要传入带“@2x”和“@3x”的标识就能正常读取到图片资源,我们要怎么处理呢?
方法1:在每处都对当前设备进行判断,并保证输入的文件名正确,即Bundle里存在带或不带标识的资源图片文件。
if(是@3x图设备) {
读取@3x资源图片路径;
}
else if (是@2x图设备) {
读取@2x资源图片路径;
}
else {
读取不带@2x和@3x资源图片路径;
}
但是请问有谁会愿意如上述方法在每个地方作这个判断呢?
方法2:给NSBundle添加Category,输入带或不带标识,自动识别对应资源图片文件。
这种方法其实是对方法1的封装,思路同方法1,但略有完善。
逻辑如下:
if(是@3x图设备) {
读取@3x图路径;
if(不存在@3x图){
读取@2x资源图片;
if(不存在@2x图){
读取@1x资源图片;
}
}
}
else if (是@2x图设备) {
读取@2x图路径;
if(不存在@2x图){
读取@3x资源图片;
if(不存在@3x图){
读取@1x资源图片;
}
}
}
else {
读取@1x资源图片;
if(不存在@1x图){
读取@2x资源图片;
if(不存在@2x图){
读取@3x资源图片;
}
}
}
代码实现如下:
运用Runtime知识,在类方法 load 里作方法替换:
+ (void)load {
Method originMethod = class_getInstanceMethod(self, @selector(pathForResource:ofType:));
Method newMethod = class_getInstanceMethod(self, @selector(tempPathForResource:ofType:));
method_exchangeImplementations(originMethod, newMethod);
}
替换的方法为:
- (NSString *)tempPathForResource:(NSString *)name ofType:(NSString *)ext {
NSString *path = [self tempPathForResource:name ofType:ext];
if (path) {
return path;
}
CGFloat scale = [UIScreen mainScreen].scale;
if (ABS(scale-3) <= 0.001) {
path = [self tempPathForResource_3x:name ofType:ext];
if (!path) {
path = [self tempPathForResource_2x:name ofType:ext];
if (!path) {
path = [self tempPathForResource_x:name ofType:ext];
}
}
}
else if (ABS(scale-2) <= 0.001){
path = [self tempPathForResource_2x:name ofType:ext];
if (!path) {
path = [self tempPathForResource_3x:name ofType:ext];
if (!path) {
path = [self tempPathForResource_x:name ofType:ext];
}
}
}
else {
path = [self tempPathForResource_x:name ofType:ext];
if (!path) {
path = [self tempPathForResource_2x:name ofType:ext];
if (!path) {
path = [self tempPathForResource_3x:name ofType:ext];
}
}
}
return path;
}
在这个方法里,优先使用原生系统的方法,如果资源能找到即返回了资源图片的path,则直接返回;否则进入下面的查找流程。在每一次查找结束后均进行判断,如果查找成功跳出if判断并返回查找到的path,否则进入下一种设备的查找。其中,查找@2x图还是@3x图,通过下面这个值判断的:
CGFloat scale = [UIScreen mainScreen].scale;
在使用变量 scale 进行判断的时候,使用的是“ABS(差) <= 0.001”方式,因为UIScreen对象的属性“scale”是一个CGFloat类型的值:
在查找资源图片的时候有这么一个问题,如果当前设备是@3x的设备,如iPhone6 Plus 或 iPhone7 Plus 或其它需要@3x图资源的设备,但我们添加进来的是@2x图资源或@1x图资源,即这正是本文要解决的问题。
针对倍率不同的设备,处理的逻辑也是不一样的。
对@1x图的设备:
if(输入的资源图片名为@3x的){
把"@3x"去掉;
}
else if (输入的资源图片名为@2x的) {
把"@2x"去掉;
}
else {
不作处理;
}
调用原生系统方法读取path;
对@2x图的设备:
if(输入的资源图片名为@3x的){
把"@3x"替换为"@2x";
}
else if (输入的资源图片名为@2x的) {
不作处理;
}
else {
给资源图片名加"@2x"后缀;
}
调用原生系统方法读取path;
对@3x图的设备:
if(输入的资源图片名为@3x的){
不作处理;
}
else if (输入的资源图片名为@2x的) {
把"@2x"替换为"@3x";
}
else {
给资源图片名加"@3x"后缀;
}
调用原生系统方法读取path;
以上三种逻辑的代码分别如下:
对@1x图的设备:
- (NSString *)tempPathForResource_x:(NSString *)name ofType:(NSString *)ext {
NSString *path = nil;
NSString *teampName = nil;
if ([name hasSuffix:@"@3x"]) {
teampName = [name stringByReplacingOccurrencesOfString:@"@3x" withString:@""];
}
else if ([name hasSuffix:@"@2x"]) {
teampName = [name stringByReplacingOccurrencesOfString:@"@2x" withString:@""];
}
else {
teampName = name;
}
path = [self tempPathForResource:teampName ofType:ext];
return path;
}
对@2x图的设备:
- (NSString *)tempPathForResource_2x:(NSString *)name ofType:(NSString *)ext {
NSString *path = nil;
NSString *teampName = nil;
if ([name hasSuffix:@"@3x"]) {
teampName = [name stringByReplacingOccurrencesOfString:@"@3x" withString:@"@2x"];
}
else if ([name hasSuffix:@"@2x"]) {
teampName = name;
}
else {
teampName = [NSString stringWithFormat:@"%@@2x", name];
}
path = [self tempPathForResource:teampName ofType:ext];
return path;
}
对@3x图的设备:
- (NSString *)tempPathForResource_3x:(NSString *)name ofType:(NSString *)ext {
NSString *path = nil;
NSString *teampName = nil;
if ([name hasSuffix:@"@3x"]) {
teampName = name;
}
else if ([name hasSuffix:@"@2x"]) {
teampName = [name stringByReplacingOccurrencesOfString:@"@2x" withString:@"@3x"];
}
else {
teampName = [NSString stringWithFormat:@"%@@3x", name];
}
path = [self tempPathForResource:teampName ofType:ext];
return path;
}
通过上述处理后,测试结果如下:
本文源代码见:
https://github.com/zhoushejun/iOSNotes/blob/master/SJNotes/Classes/UI/Utilities/Categories/NSBundle%2BResource
参考资料:
http://blog.csdn.net/null29/article/details/53640179
http://www.jianshu.com/p/f40313d37049