本文只針對經過NSBundle對象的方法 pathForResource 獲取本地圖片資源遇到的圖片名沒法自動識別@2x與@3x名稱的問題進行測試、總結與分享。git
加載本地圖片資源的方式通常經過如下兩種方法:
github
第1種:ide
UIImage *img = [UIImage imageNamed:@"imageName"];
第2種:測試
UIImage *img = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"imageName" ofType:@"imageType"]];
注:其餘方法如NSData等本文不涉及,如需瞭解請找某哥或某娘,謝謝合做。spa
假定咱們都知道第1種方法適合讀取重複使用且佔用內存小的圖片資源,且能根據當前手機硬件能自動識別「@2x」圖或「@3x」圖。但若是須要加載不常使用且佔用內存很大如上百kb甚至上M的圖片資源的時候還使用這方式,內存佔用勢必會很嚴重。解決這種加載圖片資源佔用內存問題首選方案是換到第2種,但傳入的資源名必須與「.後綴名」前的名稱一致,若是資源名添加了「@2x」或「@3x」,而傳入的resource名稱帶或不帶「@2x」或「@3x」標識,結果分別會是怎麼樣的呢?下面咱們來測試一下。.net
> 不帶「@2x」或「@3x」標識:3d
> 帶「@2x」或「@3x」標識orm
顯然傳入的名稱帶標識後能正常獲取到圖片資源。對象
但如今我就是想能過第2種方法加載本地圖片資源能像第1種方法同樣,不須要傳入帶「@2x」和「@3x」的標識就能正常讀取到圖片資源,咱們要怎麼處理呢?
blog
方法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; }
經過上述處理後,測試結果以下:
本文源代碼見:
參考資料: