1. 程式人生 > >iOS開發之0行代碼加載NSBundle中的@2x與@3x圖片

iOS開發之0行代碼加載NSBundle中的@2x與@3x圖片

ios nsbundle pathforresource

本文只針對通過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


本文出自 “江山風雨” 博客,請務必保留此出處http://965678322.blog.51cto.com/4935622/1982197

iOS開發之0行代碼加載NSBundle中的@2x與@3x圖片