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种方法适合读取重复使用且占用内存小的图片资源,且能根据当前手机硬件能th自动识别“@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图){
               读取不带@2x和@3x资源图片;
           }
        }
    }
    else if (是@2x图设备) {
        读取@2x图路径;
        if(不存在@2x图){
           读取@3x资源图片;
           if(不存在@3x图){
               读取不带@2x和@3x资源图片;
           }
        }
    }
    else {
        读取不带@2x和@3x资源图片;
        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

时间: 2024-10-12 03:19:54

iOS开发之0行代码加载NSBundle中的@2x与@3x图片的相关文章

IOS开发之Bug--View是懒加载导致出误以为是UI加载的bug

虽然分类为bug,但也算的上是一个问题,一个很简单的问题.先来看看问题的重现,就写了简单的Demo验证效果: 问题:点击ViewController跳转到TwoViewController,发现会延迟一下才出现. 这个问题也是在我工作开发中偶然遇到的,一开始不知道是什么原因.后来发现只要将TwoViewController中的: 箭头指向的一行代码注释去掉,或者添加一行关于任何view的任何操作,比如设置title.设置背景颜色.添加一个view等等操作,就可以避免苹果原生上面我遇到的问题. 解

iOS开发之XML解析代码

iOS开发之XML解析代码 //1.加载和解析XML文件 NSString *path = [[NSBundle mainBundle] pathForResource:@"xml.txt" ofType:nil]; NSData *data = [[NSData alloc] initWithContentsOfFile:path]; // GDataXMLDocument 表示XML文档对象 // initWithData 使用NSData初始化, 就是解析 GDataXMLDoc

同过代码 加载 storyboard 中的 控制器 controller

一.通过代码加载storyboard文件创建控制器的view   Test.storyboard  前名是文件名,后面的storyboard是文件的扩展名 // 1. 应用程序启动完成,会调用此方法,启动之后,将不再调用此方法!// 如果因为内存等原因,应用程序被操作系统干掉,再次点击图标,会调用此方法! - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)l

TangIDE游戏开发之60行代码实现打地鼠

对于没有接触过html的朋友,要实现一个最简单的html5打地鼠游戏,也是不知道从何下手的.就算是html高手,没有上千行代码,也完不成一个像样的打地鼠游戏. 网上找到一个开源的打地鼠游戏(参见下载地址),粗略看了一下代码,代码行数1000+. 今天我要介绍的打地鼠游戏,主要逻辑代码,也就60多行,所有的代码加起来也就100多行,是为某客户定制的一款比较完整的商业游戏,大部分的功能也就只是一下午的时间就能完成. 先看一下实际的成果: PC点击这里玩 手机下方扫描二维码玩 如果你想在本游戏上进行改

Android开发之Bitmap的高效加载

BitmapFactory类提供了四类方法:decodeFile, decodeResource, decodeStream和decodeByteArray 分别用于支持从文件系统,资源,输入流以及字节数组中加载出一个Bitmap对象,前两者又间接调用了decodeStream 为了避免OOM,可以通过采样率有效的加载图片 如果获取采样率? 1.将BitmapFactory.Options的inJustDecodeBounds的参数设置为true并加载图片 2.从BitmapFactory.Op

iOS开发swift版异步加载网络图片(带缓存和缺省图片)

iOS开发之swift版异步加载网络图片 与SDWebImage异步加载网络图片的功能相似,只是代码比较简单,功能没有SD的完善与强大,支持缺省添加图片,支持本地缓存. 异步加载图片的核心代码如下:  func setZYHWebImage(url:NSString?, defaultImage:NSString?, isCache:Bool){         var ZYHImage:UIImage?         if url == nil {             return   

【IOS开发之Objective-C】协议和代理

在现实生活中我们存在各种各样的协议,但是都有一个共同点,就是拟定的协议,就要遵守,不遵守就是违约.在OC中也有协议这一个概念而且和我们现实生活中的协议的特点是类似的.有时我们自己想做什么事,但是现在没这个能力自己去做,亲力亲为,我们就需要找代理来帮我们做了.那么在OC中也有代理这个概念.下面就简单的说说OC中的协议和代理. 一.协议 在<[IOS开发之Objective-C]对象的交互>中实现了一种对象的交互的方式.在OC中还有其他方式,比如说协议,在OC中用协议来规范接口,是实现对象之间的交

IOS开发之XML解析以及下拉刷新上拉加载更多的分享

IOS开发之XML解析 1.XML格式 <?xml version="1.0" encoding="utf-8" ?> 表示XML文件版本, 内部文本使用的编码 <root> 表示根节点 <CityName>北京</CityName>  一个结点, CityName是结点名, 北京结点值 <Item key="1" value="A"></Item>  

iOS开发之17个常用代码整理

1.判断邮箱格式是否正确的代码 //利用正则表达式验证 -(BOOL)isValidateEmail:(NSString *)email { NSString *emailRegex = @"[A-Z0-9a-z._%+-][email protected][A-Za-z0-9.-]+\\.[A-Za-z]{2,4}"; NSPredicate *emailTest = [NSPredicate predicateWithFormat:@"SELF MATCHES%@&quo